Team:EPF-Lausanne/PrimerDesignHelper.js

From 2012.igem.org

Revision as of 11:14, 28 July 2012 by Sander.kromwijk (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

/* Immutable class that makes it possible to work with (very simplified) DNA sequences

  • /

var DNA,

 __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

DNA = (function() {

 DNA.allowed = ["A", "C", "G", "T"];
 DNA.extendedAllowed = ["A", "C", "G", "T", "R", "Y", "K", "M", "S", "W", "B", "D", "H", "V", "N"];
 DNA.RS = {
   AatI: "AGGCCT",
   AatII: "GACGTC",
   AccI: "GTMKAC",
   AccIII: "TCCGGA",
   Acc65I: "GGTACC",
   AcsI: "RAATTY",
   AcyI: "GRCGYC",
   AflI: "GGWCC",
   AflII: "CTTAAG",
   AflIII: "ACRYGT",
   AgeI: "ACCGGT",
   AhaII: "GRCGYC",
   AhaIII: "TTTAAA",
   AluI: "AGCT",
   Alw44I: "GTGCAC",
   AlwNI: "CAGNNNCTG",
   AocI: "CCTNAGG",
   AosI: "TGCGCA",
   ApaI: "GGGCCC",
   ApaLI: "GTGCAC",
   ApoI: "RAATTY",
   ApyI: "CCWGG",
   AscI: "GGCGCGCC",
   AseI: "ATTAAT",
   AsnI: "ATTAAT",
   AspI: "GACNNNGTC",
   Asp700: "GAANNNNTTC",
   Asp718: "GGTACC",
   AspEI: "GACNNNNNGTC",
   AspHI: "GWGCWC",
   AsuII: "TTCGAA",
   AvaI: "CYCGRG",
   AvaII: "GGWCC",
   AviII: "TGCGCA",
   AvrII: "CCTAGG",
   BalI: "TGGCCA",
   BamHI: "GGATCC",
   BanI: "GGYRCC",
   BanII: "GRGCYC",
   BbrPI: "CACGTG",
   BclI: "TGATCA",
   BfaI: "CTAG",
   BfrI: "CTTAAG",
   BglI: "GCCNNNNNGGC",
   BglII: "AGATCT",
   BinI: "CCTAGG",
   BmyI: "GDGCHC",
   Bpu1102I: "GCTNAGC",
   BsaAI: "YACGTR",
   BsaBI: "GATNNNNATC",
   BsaHI: "GRCGYC",
   BsaJI: "CCNNGG",
   BseAI: "TCCGGA",
   BsePI: "GCGCGC",
   BsiEI: "CGRYCG",
   BsiWI: "CGTACG",
   BsiYI: "CCNNNNNNNGG",
   BslI: "CCNNNNNNNGG",
   Bsp1286I: "GDGCHC",
   Bsp1407I: "TGTACA",
   BspDI: "ATCGAT",
   BspEI: "TCCGGA",
   BspHI: "TCATGA",
   BspLU11I: "ACATGT",
   BsrFI: "RCCGGY",
   BssGI: "CCANNNNNNTGG",
   BssHII: "GCGCGC",
   Bst1107I: "GTATAC",
   BstBI: "TTCGAA",
   BstEII: "GGTNACC",
   BstNI: "CCWGG",
   BstUI: "CGCG",
   BstXI: "CCANNNNNNTGG",
   BstYI: "RGATCY",
   Bsu36I: "CCTNAGG",
   CelII: "GCTNAGC",
   CfoI: "GCGC",
   CfrI: "YGGCCR",
   Cfr10I: "RCCGGY",
   ClaI: "ATCGAT",
   DdeI: "CTNAG",
   DpnII: "GATC",
   DraI: "TTTAAA",
   DraII: "RGGNCCY",
   DraIII: "CACNNNGTG",
   DrdI: "GACNNNNNNGTC",
   DsaI: "CCRYGG",
   EaeI: "YGGCCR",
   EagI: "CGGCCG",
   Eam1105I: "GACNNNNNGTC",
   Ecl136II: "GAGCTC",
   EclXI: "CGGCCG",
   Eco47III: "AGCGCT",
   EcoNI: "CCTNNNNNAGG",
   EcoO109I: "RGGNCCY",
   EcoRI: "GAATTC",
   EcoRII: "CCWGG",
   EcoRV: "GATATC",
   EspI: "GCTNAGC",
   FnuDII: "CGCG",
   Fnu4HI: "GCNGC",
   FseI: "GGCCGGCC",
   FspI: "TGCGCA",
   HaeII: "RGCGCY",
   HaeIII: "GGCC",
   HgiAI: "GWGCWC",
   HhaI: "GCGC",
   HincII: "GTYRAC",
   HindII: "GTYRAC",
   HindIII: "AAGCTT",
   HinfI: "GANTC",
   HinPI: "GCGC",
   HpaI: "GTTAAC",
   HpaII: "CCGG",
   ItaI: "GCNGC",
   KasI: "GGCGCC",
   KpnI: "GGTACC",
   KspI: "CCGCGG",
   MaeI: "CTAG",
   MaeII: "ACGT",
   MaeIII: "GTNAC",
   MamI: "GATNNNNATC",
   MboI: "GATC",
   MfeI: "CAATTG",
   MluI: "ACGCGT",
   MluNI: "TGGCCA",
   MroI: "TCCGGA",
   MscI: "TGGCCA",
   MseI: "TTAA",
   MspI: "CCGG",
   MstI: "TGCGCA",
   MstII: "CCTNAGG",
   MunI: "CAATTG",
   MvaI: "CCWGG",
   MvnI: "CGCG",
   NaeI: "GCCGGC",
   NarI: "GGCGCC",
   NciI: "CCSGG",
   NcoI: "CCATGG",
   NdeI: "CATATG",
   NdeII: "GATC",
   NgoMI: "GCCGGC",
   NheI: "GCTAGC",
   NlaIII: "CATG",
   NlaIV: "GGNNCC",
   NotI: "GCGGCCGC",
   NruI: "TCGCGA",
   NsiI: "ATGCAT",
   NspBII: "CMGCKG",
   NspI: "RCATGY",
   NspII: "GDGCHC",
   NspV: "TTCGAA",
   PacI: "TTAATTAA",
   PaeR7I: "CTCGAG",
   PflMI: "CCANNNNNTGG",
   PinAI: "ACCGGT",
   PsiI: "TTATAA",
   PmaCI: "CACGTG",
   PmeI: "GTTTAAAC",
   PmlI: "CACGTG",
   PpuMI: "RGGWCCY",
   Psp1406I: "AACGTT",
   PstI: "CTGCAG",
   PvuI: "CGATCG",
   PvuII: "CAGCTG",
   RcaI: "TCATGA",
   RmaI: "CTAG",
   RsaI: "GTAC",
   RsrII: "CGGWCCG",
   SacI: "GAGCTC",
   SacII: "CCGCGG",
   SalI: "GTCGAC",
   SauI: "CCTNAGG",
   Sau3AI: "GATC",
   Sau96I: "GGNCC",
   ScaI: "AGTACT",
   ScrFI: "CCNGG",
   SexAI: "ACCWGGT",
   SfcI: "CTRYAG",
   SfiI: "GGCCNNNNNGGCC",
   SfuI: "TTCGAA",
   SgrAI: "CRCCGGYG",
   SmaI: "CCCGGG",
   SnaBI: "TACGTA",
   SnoI: "GTGCAC",
   SpeI: "ACTAGT",
   SphI: "GCATGC",
   SrfI: "GCCCGGGC",
   Sse8387I: "CCTGCAGG",
   SspI: "AATATT",
   SspBI: "TGTACA",
   SstI: "GAGCTC",
   SstII: "CCGCGG",
   StuI: "AGGCCT",
   StyI: "CCWWGG",
   SwaI: "ATTTAAAT",
   TaqI: "TCGA",
   TfiI: "GAWTC",
   ThaI: "CGCG",
   Tru9I: "TTAA",
   Tth111I: "GACNNNGTC",
   Van91I: "CCANNNNNTGG",
   XbaI: "TCTAGA",
   XcmI: "CCANNNNNNNNNTGG",
   XhoI: "CTCGAG",
   XhoII: "RGATCY",
   XmaI: "CCCGGG",
   XmaIII: "CGGCCG",
   XmnI: "GAANNNNTTC"
 };
 DNA.cutPositions = {
   AatI: 3,
   AatII: 5,
   AccI: 2,
   AccIII: 1,
   Acc65I: 1,
   AcsI: 1,
   AcyI: 2,
   AflI: 1,
   AflII: 1,
   AflIII: 1,
   AgeI: 1,
   AhaII: 2,
   AhaIII: 3,
   AluI: 2,
   Alw44I: 1,
   AlwNI: 6,
   AocI: 2,
   AosI: 3,
   ApaI: 5,
   ApaLI: 1,
   ApoI: 1,
   ApyI: 0,
   AscI: 2,
   AseI: 2,
   AsnI: 2,
   AspI: 4,
   Asp700: 5,
   Asp718: 1,
   AspEI: 6,
   AspHI: 5,
   AsuII: 2,
   AvaI: 1,
   AvaII: 1,
   AviII: 3,
   AvrII: 1,
   BalI: 3,
   BamHI: 1,
   BanI: 1,
   BanII: 5,
   BbrPI: 3,
   BclI: 1,
   BfaI: 1,
   BfrI: 1,
   BglI: 7,
   BglII: 1,
   BinI: 1,
   BmyI: 5,
   Bpu1102I: 2,
   BsaAI: 3,
   BsaBI: 5,
   BsaHI: 2,
   BsaJI: 1,
   BseAI: 1,
   BsePI: 1,
   BsiEI: 4,
   BsiWI: 1,
   BsiYI: 7,
   BslI: 7,
   Bsp1286I: 5,
   Bsp1407I: 1,
   BspDI: 2,
   BspEI: 1,
   BspHI: 1,
   BspLU11I: 1,
   BsrFI: 1,
   BssGI: 8,
   BssHII: 1,
   Bst1107I: 3,
   BstBI: 2,
   BstEII: 1,
   BstNI: 2,
   BstUI: 2,
   BstXI: 8,
   BstYI: 1,
   Bsu36I: 2,
   CelII: 2,
   CfoI: 3,
   CfrI: 1,
   Cfr10I: 1,
   ClaI: 2,
   DdeI: 1,
   DpnII: 0,
   DraI: 3,
   DraII: 2,
   DraIII: 6,
   DrdI: 7,
   DsaI: 1,
   EaeI: 1,
   EagI: 1,
   Eam1105I: 6,
   Ecl136II: 3,
   EclXI: 1,
   Eco47III: 3,
   EcoNI: 5,
   EcoO109I: 2,
   EcoRI: 1,
   EcoRII: 0,
   EcoRV: 3,
   EspI: 2,
   FnuDII: 2,
   Fnu4HI: 2,
   FseI: 6,
   FspI: 3,
   HaeII: 5,
   HaeIII: 2,
   HgiAI: 5,
   HhaI: 3,
   HincII: 3,
   HindII: 3,
   HindIII: 1,
   HinfI: 1,
   HinPI: 1,
   HpaI: 3,
   HpaII: 1,
   ItaI: 2,
   KasI: 1,
   KpnI: 5,
   KspI: 4,
   MaeI: 1,
   MaeII: 1,
   MaeIII: 0,
   MamI: 5,
   MboI: 0,
   MfeI: 1,
   MluI: 1,
   MluNI: 3,
   MroI: 1,
   MscI: 3,
   MseI: 1,
   MspI: 1,
   MstI: 3,
   MstII: 2,
   MunI: 1,
   MvaI: 2,
   MvnI: 2,
   NaeI: 3,
   NarI: 2,
   NciI: 2,
   NcoI: 1,
   NdeI: 2,
   NdeII: 0,
   NgoMI: 1,
   NheI: 1,
   NlaIII: 4,
   NlaIV: 3,
   NotI: 2,
   NruI: 3,
   NsiI: 5,
   NspBII: 3,
   NspI: 5,
   NspII: 5,
   NspV: 2,
   PacI: 5,
   PaeR7I: 1,
   PflMI: 7,
   PinAI: 1,
   PsiI: 3,
   PmaCI: 3,
   PmeI: 4,
   PmlI: 3,
   PpuMI: 2,
   Psp1406I: 2,
   PstI: 5,
   PvuI: 4,
   PvuII: 3,
   RcaI: 1,
   RmaI: 1,
   RsaI: 2,
   RsrII: 2,
   SacI: 5,
   SacII: 4,
   SalI: 1,
   SauI: 2,
   Sau3AI: 0,
   Sau96I: 1,
   ScaI: 3,
   ScrFI: 2,
   SexAI: 1,
   SfcI: 1,
   SfiI: 8,
   SfuI: 2,
   SgrAI: 2,
   SmaI: 3,
   SnaBI: 3,
   SnoI: 1,
   SpeI: 1,
   SphI: 5,
   SrfI: 4,
   Sse8387I: 6,
   SspI: 3,
   SspBI: 1,
   SstI: 5,
   SstII: 4,
   StuI: 3,
   StyI: 1,
   SwaI: 4,
   TaqI: 1,
   TfiI: 1,
   ThaI: 2,
   Tru9I: 1,
   Tth111I: 4,
   Van91I: 7,
   XbaI: 1,
   XcmI: 8,
   XhoI: 1,
   XhoII: 1,
   XmaI: 1,
   XmaIII: 1,
   XmaCI: 1,
   XmnI: 5
 };
 DNA.RSFlipped = void 0;
 DNA.getRSFlipped = function() {
   var key, name, seq, value, _ref, _ref2;
   if (this.RSFlipped === void 0) {
     this.RSFlipped = {};
     _ref = this.RS;
     for (name in _ref) {
       seq = _ref[name];
       if (this.RSFlipped[seq]) {
         this.RSFlipped[seq].push(name);
       } else {
         this.RSFlipped[seq] = [name];
       }
     }
     _ref2 = this.RSFlipped;
     for (key in _ref2) {
       value = _ref2[key];
       if (value.length <= 2) {
         this.RSFlipped[key] = value.join(" or ");
       } else {
         this.RSFlipped[key] = value.slice(0, value.length - 1).join(", ") + " or " + value[value.length - 1];
       }
     }
   }
   return this.RSFlipped;
 };
 DNA.getCutPosition = function(rs) {
   var k, v, _ref;
   if (DNA.cutPositions[rs]) {
     return DNA.cutPositions[rs];
   } else {
     _ref = DNA.cutPositions;
     for (k in _ref) {
       v = _ref[k];
       if (k.toLowerCase() === rs.toLowerCase()) return v;
     }
     return -1;
   }
 };
 DNA.nucleotideMatches = {
   A: ["A", "R", "M", "W", "D", "H", "V", "N"],
   C: ["C", "Y", "M", "S", "B", "H", "V", "N"],
   G: ["G", "R", "K", "S", "B", "D", "V", "N"],
   T: ["T", "Y", "K", "W", "B", "D", "H", "N"],
   R: ["A", "G"],
   Y: ["C", "T"],
   K: ["G", "T"],
   M: ["A", "C"],
   S: ["C", "G"],
   W: ["A", "T"],
   B: ["C", "G", "T"],
   D: ["A", "G", "T"],
   H: ["A", "C", "T"],
   V: ["A", "C", "G"],
   N: ["A", "C", "G", "T"]
 };
 DNA.fromString = function(data) {
   var found, name, seq, sequence, _ref;
   found = false;
   if (data != null) {
     _ref = this.RS;
     for (name in _ref) {
       sequence = _ref[name];
       if (name.toLowerCase() === data.toLowerCase()) {
         seq = new DNA(sequence.split());
         found = true;
       }
     }
   } else {
     data = "";
   }
   if (!found) seq = new DNA(data.toUpperCase().replace(/\s/ig, "").split());
   seq.validateData();
   return seq;
 };
 DNA.fromRandom = function(length) {
   var data, i;
   if (length === 0 || length === "0" || !length || length === void 0) {
     return new DNA([]);
   } else {
     data = (function() {
       var _results;
       _results = [];
       for (i = 1; 1 <= length ? i <= length : i >= length; 1 <= length ? i++ : i--) {
         _results.push(DNA.allowed[Math.floor(Math.random() * DNA.allowed.length)]);
       }
       return _results;
     })();
     return new DNA(data);
   }
 };
 /*
   The constructor assumes the data has been validated (for performance reasons -> premature and non-consequent
   optimization ^^). Anyway, the DNA.fromString function is probably what you were looking for!
 */
 function DNA(data) {
   this.data = data;
 }
 DNA.prototype.validateData = function(strict) {
   if (strict) {
     if (this.data.some(function(x) {
       return __indexOf.call(DNA.allowed, x) < 0;
     })) {
       throw new Error("Invalid character encountered");
     }
   } else {
     if (this.data.some(function(x) {
       return __indexOf.call(DNA.extendedAllowed, x) < 0;
     })) {
       throw new Error("Invalid character encountered");
     }
   }
 };
 DNA.prototype.matches = function(seq2) {
   var combined;
   if (this.length() !== seq2.length()) return false;
   combined = _.zip(this.data, seq2.data);
   return _.all(combined, function(_arg) {
     var i, j, n1, n1Matches, n2, n2Matches, _ref, _ref2;
     n1 = _arg[0], n2 = _arg[1];
     if (n1 === n2) {
       return true;
     } else {
       if (__indexOf.call(DNA.allowed, n1) >= 0) {
         return __indexOf.call(DNA.nucleotideMatches[n1], n2) >= 0;
       }
       if (__indexOf.call(DNA.allowed, n2) >= 0) {
         return __indexOf.call(DNA.nucleotideMatches[n2], n1) >= 0;
       }
       n1Matches = DNA.nucleotideMatches[n1];
       n2Matches = DNA.nucleotideMatches[n2];
       for (i = 0, _ref = n1Matches.length - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) {
         for (j = 0, _ref2 = n2Matches.length - 1; 0 <= _ref2 ? j <= _ref2 : j >= _ref2; 0 <= _ref2 ? j++ : j--) {
           if (n1Matches[i] === n2Matches[j]) return true;
         }
       }
       return false;
     }
   });
 };
 DNA.prototype.findRS = function(nameOnly) {
   var key, name, rs, rsDNA, seq, _ref;
   if (nameOnly == null) nameOnly = false;
   if (this.length() > 15) return "";
   key = this.data.join("");
   rs = DNA.getRSFlipped();
   if (rs[key]) {
     if (nameOnly) {
       return rs[key];
     } else {
       return rs[key] + " (5' → 3': " + key + ")";
     }
   } else {
     _ref = DNA.RS;
     for (seq in _ref) {
       name = _ref[seq];
       rsDNA = DNA.fromString(seq);
       if (this.matches(rsDNA)) {
         if (nameOnly) {
           name;
         } else {
           name + " (5' → 3': " + key + ")";
         }
       }
     }
     return "";
   }
 };
 DNA.prototype.getCutPosition = function() {
   return DNA.getCutPosition(this.findRS(true));
 };
 DNA.prototype.containsRS = function(names) {
   var c, comparisons, i, lengths, name, parts, pos, rs, _i, _j, _len, _len2, _ref;
   comparisons = [];
   for (_i = 0, _len = names.length; _i < _len; _i++) {
     name = names[_i];
     comparisons.push(name.toLowerCase());
   }
   lengths = (function() {
     var _results;
     _results = [];
     for (i = 0; i <= 10; i++) {
       _results.push("");
     }
     return _results;
   })();
   rs = DNA.getRSFlipped();
   pos = 0;
   _ref = this.data;
   for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
     c = _ref[_j];
     for (i = 4; i <= 10; i++) {
       lengths[i] += c;
       if (i <= pos) lengths[i] = lengths[i].substr(1);
       if (rs[lengths[i]]) {
         parts = rs[lengths[i]].toLowerCase().split(" ");
         if (_.some(comparisons, function(obj) {
           return __indexOf.call(parts, obj) >= 0;
         })) {
           return true;
         }
       }
     }
     pos++;
   }
   return false;
 };
 DNA.prototype.length = function() {
   return this.data.length;
 };
 DNA.prototype.snip = function(length) {
   if (length >= 0 && length <= this.data.length) {
     return new DNA(this.data.slice(0, length));
   } else {
     throw new Error("Invalid snip length (tried to snip a piece of length " + length + " in a sequence of " + this.length() + "bp)");
   }
 };
 DNA.prototype.reverseComplement = function() {
   var complement, k, l, revCompl, v, _len, _ref;
   revCompl = [];
   complement = function(x) {
     switch (x) {
       case "A":
         return "T";
       case "T":
         return "A";
       case "C":
         return "G";
       case "G":
         return "C";
       default:
         throw new Error("Invalid character encountered (" + x + ")");
     }
   };
   l = this.data.length - 1;
   _ref = this.data;
   for (k = 0, _len = _ref.length; k < _len; k++) {
     v = _ref[k];
     revCompl[l - k] = complement(v);
   }
   return new DNA(revCompl);
 };
 DNA.prototype.append = function(seqToAdd) {
   return new DNA(this.data.concat(seqToAdd.data));
 };
 DNA.prototype.tm = function() {
   return TmCalc.calc(this.data);
 };
 DNA.prototype.toString = function() {
   return this.data.join("");
 };
 DNA.prototype.toJSON = function() {
   return this.toString();
 };
 DNA.prototype.clone = function() {
   return new DNA(this.data.slice(0));
 };
 return DNA;

})(); var DNAInput, DNARSInput, DNASmallInput, Input, InputForm, NumberInput, RadioInput, SelectInput, Separator, Submit, TemperatureInput,

 __hasProp = Object.prototype.hasOwnProperty,
 __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };

InputForm = (function() {

 InputForm.i = 0;
 /*
   [{
     name: "tm",
     readableName: "Tm",
     type: "TemperatureInput",
     label: "Tm"
   }]
 */
 InputForm.create = function(options, objects) {
   var f;
   f = new InputForm(options);
   objects.forEach(function(x) {
     return f.addInput(x);
   });
   return f;
 };
 function InputForm(_arg) {
   this.update = _arg.update, this.submit = _arg.submit, this.name = _arg.name;
   this.id = "inputform-" + InputForm.i++;
   this.inputs = [];
   this.inputDict = {};
   this.lastValue = {};
 }
 InputForm.prototype.save = function(data) {
   var k, tempData, v;
   try {
     if (JSON && localStorage) {
       tempData = {};
       for (k in data) {
         v = data[k];
         if (k === "leftRS" || k === "rightRS") v = v.rs;
         tempData[k] = v;
       }
       return localStorage.setItem("values", JSON.stringify(tempData));
     }
   } catch (e) {
   }
 };
 InputForm.prototype.restore = function() {
   var k, oldVals, v, _results;
   try {
     if (JSON && localStorage) {
       oldVals = localStorage.getItem("values");
       if (oldVals != null) {
         oldVals = JSON.parse(oldVals);
         _results = [];
         for (k in oldVals) {
           v = oldVals[k];
           _results.push(this.inputDict[k].value(v));
         }
         return _results;
       }
     }
   } catch (e) {
   }
 };
 InputForm.prototype.attach = function(elem) {
   $(elem).append(this.generateDOM());
   $(".help-block").hide();
   this.restore();
   return this.updateCallback();
 };
 InputForm.prototype.submitCallback = function() {
   var changed, data, error, messages, _ref;
   _ref = this.getFormData(), data = _ref.data, messages = _ref.messages, error = _ref.error, changed = _ref.changed;
   return typeof this.submit === "function" ? this.submit(data, messages, error) : void 0;
 };
 InputForm.prototype.updateCallback = function() {
   var changed, data, error, messages, _ref;
   if (this.disableAuto) return;
   _ref = this.getFormData(), data = _ref.data, messages = _ref.messages, error = _ref.error, changed = _ref.changed;
   if (changed) {
     return typeof this.update === "function" ? this.update(data, messages, error) : void 0;
   }
 };
 InputForm.prototype.getFormData = function() {
   var changed, data, error, messages;
   data = {};
   messages = [];
   error = false;
   this.inputs.forEach(function(x) {
     var val;
     val = void 0;
     try {
       val = x.value();
     } catch (e) {
       messages.push("Error: " + e.message);
     }
     if (!x.validate(val)) {
       error = true;
       messages.push("The value of " + x.get("label") + " is invalid");
     }
     if (x.get("name") != null) return data[x.get("name")] = val;
   });
   changed = false;
   if (!_.isEqual(this.lastValue, data)) {
     changed = true;
     this.inputs.forEach(function(x) {
       if (x.visible(data)) {
         return x.show();
       } else {
         return x.hide();
       }
     });
     this.lastValue = data;
     this.save(data);
   }
   return {
     data: data,
     messages: messages,
     error: error,
     changed: changed
   };
 };
 InputForm.prototype.addInput = function(input) {
   var callback, inputObj,
     _this = this;
   if (input === "Separator") {
     this.inputs.push(new Separator());
     return;
   }
   if (!window[input.type] instanceof Function) {
     throw new Error("Passed input can't be instantiated");
   }
   callback = function() {
     return _this.updateCallback.apply(_this, arguments);
   };
   if (input.type === "Submit") {
     callback = function() {
       return _this.submitCallback.apply(_this, arguments);
     };
   }
   inputObj = new window[input.type](callback, input);
   if (!inputObj instanceof Input) throw new Error("Object isn't an input");
   this.inputDict[input.name] = inputObj;
   this.inputs.push(inputObj);
   return this;
 };
 InputForm.prototype.get = function(field) {
   return this.inputDict[field];
 };
 InputForm.prototype.generateDOM = function() {
   var fieldset, form,
     _this = this;
   form = $("<form>").attr("id", this.id).attr("class", "form-horizontal");
   if (this.name != null) {
     fieldset = $("<fieldset>").append($("<legend>").text(this.name));
     form.append(fieldset);
   } else {
     fieldset = form;
   }
   this.inputs.forEach(function(input) {
     return fieldset.append(input.generateDOM());
   });
fieldset.prepend($("
")).prepend($("
").attr("class", "control-group " + this.rowClass).append($("<label>").attr("class", "control-label").attr("for", "help-checkbox").html("Show help blocks")).append($("
").attr("class", "controls").append($("<input>").attr("class", "input-medium").attr("type", "checkbox").attr("checked", false).attr("id", "help-checkbox").on("change", function() {
     if ($("#help-checkbox").attr("checked")) {
       return $(".help-block").show();
     } else {
       return $(".help-block").hide();
     }
})))).prepend($("
").attr("class", "control-group " + this.rowClass).append($("<label>").attr("class", "control-label").attr("for", "autoUpdate-checkbox").html("Automatically update results (CPU-heavy, but way more useable)")).append($("
").attr("class", "controls").append($("<input>").attr("class", "input-medium").attr("type", "checkbox").attr("checked", "checked").attr("id", "autoUpdate-checkbox").on("change", function() {
     if ($("#autoUpdate-checkbox").attr("checked")) {
       return _this.disableAuto = false;
     } else {
       return _this.disableAuto = true;
     }
   }))));
   return form;
 };
 return InputForm;

})();

Input = (function() {

 Input.i = 0;
 function Input(callback, data, rowClass) {
   this.callback = callback;
   this.data = data;
   this.rowClass = rowClass != null ? rowClass : "";
   this.id = "input-" + Input.i++;
   this.type = "text";
 }
 Input.prototype.generateDOM = function() {
return $("
").attr("id", "row-" + this.id).attr("class", "control-group " + this.rowClass).append(this.generateLabelDOM()).append($("
").attr("class", "controls").append(this.generateElementDOM()).append(this.generateHelpDOM()));
 };
 Input.prototype.generateLabelDOM = function() {
   return $("<label>").attr("class", "control-label").attr("for", this.id).html(this.data.label);
 };
 Input.prototype.generateInputElementDOM = function(id, type, value, callback) {
   var element;
   element = $("<input>").attr("class", "input-medium").attr("type", type).attr("id", id).on("change", callback).on("keyup", callback);
   element.val(value);
   return element;
 };
 Input.prototype.generateElementDOM = function() {
   return this.generateInputElementDOM(this.id, this.type, this.data.value, this.callback);
 };
 Input.prototype.generateHelpDOM = function(helpData) {
   if (helpData == null) helpData = this.data.help;
   if ((helpData != null) && helpData.length > 0) {
return $("

").attr("class", "help-block").html(helpData); } else { return $("<p>").attr("class", "help-block").attr("style", "display: none"); } }; Input.prototype.get = function(key) { return this.data[key]; }; Input.prototype.visible = function(vals) { if (this.data.visible != null) { return this.data.visible(vals); } else { return true; } }; Input.prototype.hide = function() { return $("#row-" + this.id).hide(); }; Input.prototype.show = function() { return $("#row-" + this.id).show(); }; Input.prototype.value = function(value) { if (value === void 0) { return $("#" + this.id).val(); } else { return $("#" + this.id).val(value); } }; Input.prototype.validate = function(val) { return true; }; return Input; })(); Separator = (function(_super) { __extends(Separator, _super); function Separator() { Separator.__super__.constructor.apply(this, arguments); } Separator.prototype.generateDOM = function() { return $("


").attr("id", "row-" + this.id);
 };
 Separator.prototype.value = function() {};
 Separator.prototype.validate = function() {
   return true;
 };
 return Separator;

})(Input);

Submit = (function(_super) {

 __extends(Submit, _super);
 function Submit() {
   Submit.__super__.constructor.apply(this, arguments);
 }
 Submit.prototype.generateElementDOM = function() {
   return $("<input>").attr("type", "submit").attr("value", this.data.value).attr("class", "btn").click(this.callback, function(e) {
     e.preventDefault();
     e.data(e);
     return false;
   });
 };
 Submit.prototype.value = function() {};
 Submit.prototype.validate = function() {
   return true;
 };
 return Submit;

})(Input);

NumberInput = (function(_super) {

 __extends(NumberInput, _super);
 function NumberInput() {
   NumberInput.__super__.constructor.apply(this, arguments);
   this.type = "number";
 }
 NumberInput.prototype.value = function(value) {
   if (value === void 0) {
     return parseInt(NumberInput.__super__.value.call(this), 10);
   } else {
     return NumberInput.__super__.value.call(this, parseInt(value, 10));
   }
 };
 NumberInput.prototype.validate = function(val) {
   return !isNaN(val);
 };
 return NumberInput;

})(Input);

TemperatureInput = (function(_super) {

 __extends(TemperatureInput, _super);
 function TemperatureInput() {
   TemperatureInput.__super__.constructor.apply(this, arguments);
 }
 TemperatureInput.prototype.generateElementDOM = function() {
   var input;
   input = TemperatureInput.__super__.generateElementDOM.call(this);
return $("
").attr("class", "input-append").append(input).append($("").attr("class", "add-on").text("\u00B0C"));
 };
 return TemperatureInput;

})(NumberInput);

DNAInput = (function(_super) {

 __extends(DNAInput, _super);
 function DNAInput() {
   DNAInput.__super__.constructor.apply(this, arguments);
 }
 DNAInput.prototype.generateElementDOM = function() {
   var textarea;
   textarea = $("<textarea>").attr("class", "input-xlarge").attr("id", this.id).on("change", this.callback).on("keyup", this.callback);
   textarea.val(this.data.value);
   return textarea;
 };
 DNAInput.prototype.value = function(value) {
   if (value === void 0) {
     return DNA.fromString(DNAInput.__super__.value.call(this));
   } else {
     return DNAInput.__super__.value.call(this, value.toString());
   }
 };
 DNAInput.prototype.validate = function(val) {
   return val !== void 0;
 };
 return DNAInput;

})(Input);

DNASmallInput = (function(_super) {

 __extends(DNASmallInput, _super);
 function DNASmallInput() {
   DNASmallInput.__super__.constructor.apply(this, arguments);
 }
 DNASmallInput.prototype.generateElementDOM = function() {
   var input;
   input = DNASmallInput.__super__.generateElementDOM.call(this);
return $("
").attr("class", "input-prepend input-append").append($("").attr("class", "add-on").text("5'")).append(this.generateInputElementDOM(this.id, "text", this.data.value, this.callback)).append($("").attr("class", "add-on").text("3'"));
 };
 return DNASmallInput;

})(DNAInput);

DNARSInput = (function(_super) {

 __extends(DNARSInput, _super);
 function DNARSInput() {
   DNARSInput.__super__.constructor.apply(this, arguments);
 }
 DNARSInput.prototype.generateElementDOM = function() {
   var input;
   input = DNARSInput.__super__.generateElementDOM.call(this);
return $("
").attr("class", "input-prepend input-append").append($("").attr("class", "add-on").text("5'")).append(this.generateInputElementDOM(this.id, "text", this.data.value, this.callback)).append($("").attr("class", "add-on").text("3'")).append($("").attr("id", this.id + "-rs").attr("class", "add-on").text("append 2"));
 };
 DNARSInput.prototype.value = function(value) {
   var val;
   val = DNARSInput.__super__.value.call(this, value);
   if ((val != null) && val instanceof DNA) {
     $("#" + this.id + "-rs").html(val.toString());
   }
   if (val === void 0) val = DNA.fromString("");
   return {
     dna: val,
     rs: $("#" + this.id).val()
   };
 };
 return DNARSInput;

})(DNAInput);

SelectInput = (function(_super) {

 __extends(SelectInput, _super);
 function SelectInput(callback, obj) {
   SelectInput.__super__.constructor.apply(this, arguments);
   this.choices = obj.choices;
 }
 SelectInput.prototype.generateElementDOM = function() {
   var container, input,
     _this = this;
   input = SelectInput.__super__.generateElementDOM.call(this);
   container = $("<select>").attr("class", "input-medium").attr("id", this.id).on("change", this.callback).on("click", this.callback);
   this.choices.forEach(function(_arg) {
     var name, value;
     name = _arg.name, value = _arg.value;
     return container.append($("<option>").attr("value", name).text(value));
   });
   return container;
 };
 return SelectInput;

})(Input);

RadioInput = (function(_super) {

 __extends(RadioInput, _super);
 function RadioInput(callback, obj) {
   RadioInput.__super__.constructor.apply(this, arguments);
   this.choices = obj.choices;
 }
 RadioInput.prototype.generateElementDOM = function() {
   var container, i, input,
     _this = this;
   input = RadioInput.__super__.generateElementDOM.call(this);
container = $("
");
   i = 0;
   this.choices.forEach(function(_arg) {
     var elem, help, label, name;
     name = _arg.name, label = _arg.label, help = _arg.help;
     elem = $("<label>").attr("class", "radio").text(label).prepend($("<input>").attr("checked", _this.data.value === name).attr("type", "radio").attr("id", _this.id + "-" + name).attr("name", _this.id).on("change", _this.callback).on("click", _this.callback)).append(_this.generateHelpDOM(help));
     return container.append(elem);
   });
   return container;
 };
 RadioInput.prototype.value = function(value) {
   var output,
     _this = this;
   if (value === void 0) {
     output = {};
     this.choices.forEach(function(_arg) {
       var name;
       name = _arg.name;
       return output[name] = $("#" + _this.id + "-" + name).attr("checked") === "checked";
     });
     return output;
   } else {
     if (value instanceof String) {
       this.choices.forEach(function(_arg) {
         var name;
         name = _arg.name;
         return $("#" + _this.id + "-" + name).attr("checked", name === value);
       });
     } else {
       this.choices.forEach(function(_arg) {
         var name;
         name = _arg.name;
         return $("#" + _this.id + "-" + name).attr("checked", value[name]);
       });
     }
     return value;
   }
 };
 return RadioInput;

})(Input);

$(function() {

window.output = new Output("Primer 1 (Template:Primer1Tm\u00B0C, Template:Primer1TailLength + Template:Primer1Lengthbp): 5' → 3'
\n
{{primer1}}
\nPrimer 2 (Template:Primer2Tm\u00B0C, Template:Primer2TailLength + Template:Primer2Lengthbp): 5' → 3'
\n
{{primer2}}
\nAnnealing temperature: Template:AnnealingTm\u00B0C\n\n

\n

Messages

\nTemplate:Messages\n\n
\n

Digestion result

\nTemplate:RSDisplay:\n
\n

PCR Product

\nTemplate:DNADisplay:full\n");
 window.output.attach("#outputAttach");
 window.primerFinder = new PrimerFinder({
   output: function(data, messages) {
     window.output.setMessages(messages);
     return window.output.set(data);
   }
 });
 window.form = InputForm.create({
   name: "Primer design helper",
   submit: function(data, messages, error) {
     return window.primerFinder.calc(data, messages, error);
   },
   update: function(data, messages, error) {
     return window.primerFinder.calc(data, messages, error);
   }
 }, [
   {
     name: "dna",
     label: "DNA Sequence of interest",
     type: "DNAInput",
     value: "",
     help: "Most of the time you should just paste the DNA sequence (in the 5' → 3' direction) you want to extract/edit in this field"
   }, {
     type: "Separator"
   }, {
     name: "type",
     label: "Primer type",
     type: "RadioInput",
     choices: [
       {
         name: "normal",
         label: "Primers that extract the whole sequence",
         help: "Running a PCR using these primers will extract the entire sequence"
       }, {
         name: "rs",
         label: "Add restriction sites",
         help: "Running a PCR using these primers will result in the sequence with added standardized restriction sites on both sides."
       }, {
         name: "known",
         label: "Add a standard prefix/suffix to the sequence",
         help: "Running a PCR using these primers will result in the sequence with added standardized tails on both sides (for example for biobricking)."
       }, {
         name: "custom",
         label: "Add custom tails to the sequence",
         help: "Running a PCR using these primers will result in the sequence with added tails on both sides."
       }
     ],
     value: "normal"
   }, {
     type: "Separator"
   }, {
     name: "standardTail",
     label: "Standardized tails",
     type: "SelectInput",
     value: "biobrickGene",
     choices: [
       {
         name: "biobrickGene",
         value: "Biobrick (gene)"
       }, {
         name: "biobrickOther",
         value: "Biobrick (other)"
       }
     ],
     visible: function(_arg) {
       var type;
       type = _arg.type;
       return type.known;
     }
   }, {
     name: "randomBases",
     label: "Random Bases",
     type: "NumberInput",
     value: "4",
     help: "This is the number of random bases will be added on both ends.",
     visible: function(_arg) {
       var type;
       type = _arg.type;
       return type.rs;
     }
   }, {
     name: "leftRS",
     label: "Left restriction site",
     type: "DNARSInput",
     value: "",
     help: "You can also enter the name of the restriction enzyme (i.e. EcoRI).",
     visible: function(_arg) {
       var type;
       type = _arg.type;
       return type.rs;
     }
   }, {
     name: "rightRS",
     label: "Right restriction site",
     type: "DNARSInput",
     value: "",
     help: "You can also enter the name of the restriction enzyme (i.e. EcoRI).",
     visible: function(_arg) {
       var type;
       type = _arg.type;
       return type.rs;
     }
   }, {
     name: "leftTail",
     label: "Left tail",
     type: "DNASmallInput",
     value: "",
     visible: function(_arg) {
       var type;
       type = _arg.type;
       return type.custom;
     }
   }, {
     name: "rightTail",
     label: "Right tail",
     type: "DNASmallInput",
     value: "",
     visible: function(_arg) {
       var type;
       type = _arg.type;
       return type.custom;
     }
   }, {
     type: "Separator",
     visible: function(_arg) {
       var type;
       type = _arg.type;
       return !type.normal;
     }
   }, {
     name: "tm",
     label: "Target Tm",
     type: "TemperatureInput",
     value: "50",
     help: "The program will try to create primers that have an annealing temperature close to this one."
   }, {
     value: "Generate",
     type: "Submit"
   }
 ]);
 return window.form.attach($("#formAttach"));

});

window.tails = [

 {
   name: "Biobrick (gene)",
   value: "biobrickGene",
   primer1Tail: "GTT TCT TCG AAT TCG CGG CCG CTT CTA G",
   primer2Tail: "GTT TCT TCC TGC AGC GGC CGC TAC TAG TA TTA TTA",
   messages: ["Don't forget to include the first ATG and to exclude the stop codon!", "The recommended minimum length of a BioBrick primer is 20bp", "The recommended Tm of a BioBrick primer is between 55 and 65\u00B0C", "Check that the following restriction sites are absent: EcoRI, SpeI, XbaI, PstI, NotI", "Check out <a href='http://openwetware.org/wiki/Synthetic_Biology:BioBricks/Part_fabrication'>OpenWetWare's reference page</a>"],
   validate: function(primer1, primer2, dna) {
     var end, start;
     start = primer1.substr(0, 3).toUpperCase();
     if (start !== "ATG") return "The starting ATG seems to be absent.";
     end = primer2.substr(0, 3).toUpperCase();
     if (end === "TTA" || end === "TCA" || end === "CTA") {
       return "Remove the stop codons from the sequence!";
     }
     if (dna.containsRS(["EcoRI", "SpeI", "XbaI", "PstI", "NotI"])) {
       return "One of the following restriction sites is present (and shouldn't be): EcoRI, SpeI, XbaI, PstI, NotI";
     }
     return;
   }
 }, {
   name: "Biobrick (other)",
   value: "biobrickOther",
   primer1Tail: "GTT TCT TCG AAT TCG CGG CCG CTT CTA GAG",
   primer2Tail: "GTT TCT TCC TGC AGC GGC CGC TAC TAG TA",
   messages: ["The recommended minimum length of a BioBrick primer is 20bp", "The recommended Tm of a BioBrick primer is between 55 and 65\u00B0C", "Check that the following restriction sites are absent: EcoRI, SpeI, XbaI, PstI, NotI", "Check out <a href='http://openwetware.org/wiki/Synthetic_Biology:BioBricks/Part_fabrication'>OpenWetWare's reference page</a>"],
   validate: function(primer1, primer2, dna) {
     if (dna.containsRS(["EcoRI", "SpeI", "XbaI", "PstI", "NotI"])) {
       return "One of the following restriction sites is present (and shouldn't be): EcoRI, SpeI, XbaI, PstI, NotI";
     }
     return;
   }
 }

]; var DNADisplay, Display, Output, RSDisplay, SelfDimerDisplay,

 __hasProp = Object.prototype.hasOwnProperty,
 __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };

Output = (function() {

 Output.i = 0;
 Output.j = 0;
 function Output(template) {
   this.template = template;
   this.id = "output-" + Output.i++;
   this.fields = {};
   this.matches = {};
   this.displays = {};
   this.parsedTemplate = this.parseTemplate(this.template);
 }
 Output.prototype.attach = function(elem) {
   var displayClass, displayObj, k, v, _ref, _results;
   this.matches = {};
   $(elem).html(this.parsedTemplate);
   _ref = this.displays;
   _results = [];
   for (k in _ref) {
     v = _ref[k];
     _results.push((function() {
       var _i, _len, _ref2, _results2;
       _results2 = [];
       for (_i = 0, _len = v.length; _i < _len; _i++) {
         _ref2 = v[_i], displayClass = _ref2.displayClass, displayObj = _ref2.displayObj;
         _results2.push(displayObj.attach("." + displayClass));
       }
       return _results2;
     })());
   }
   return _results;
 };
 Output.prototype.setMessages = function(messages) {
   var output;
   output = messages;
   if (messages.length === 0) output = ["No messages"];
return this.getFields("_messages").html("
  • " + output.join("
  • ") + "
");
 };
 Output.prototype.wipe = function() {
   var k, v, _ref;
   _ref = this.fields;
   for (k in _ref) {
     v = _ref[k];
     this.updateValue(k, void 0);
   }
   return this.fields = {};
 };
 Output.prototype.set = function(values) {
   var k, v;
   for (k in values) {
     v = values[k];
     this.updateValue(k, v);
   }
   this.updateValue("__all__", this.fields);
   return this.fields;
 };
 Output.prototype.updateValue = function(k, v) {
   var displayObj, _i, _len, _ref, _results;
   if (this.fields[k] !== v) {
     this.fields[k] = v;
     if (v != null) {
       this.getFields(k).html(v.toString());
     } else {
       this.getFields(k).html("");
     }
   }
   if (this.displays[k] != null) {
     _ref = this.displays[k];
     _results = [];
     for (_i = 0, _len = _ref.length; _i < _len; _i++) {
       displayObj = _ref[_i].displayObj;
       _results.push(displayObj.set(v));
     }
     return _results;
   }
 };
 Output.prototype.getFields = function(name) {
   if (!this.matches[name]) {
     this.matches[name] = $("." + this.id + "-match-" + name);
   }
   return this.matches[name];
 };
 Output.prototype.parseTemplate = function(template) {
   var c1, c2, display, displayClass, displayObj, displayPart, i, inName, key, name, output, skipNext, started, _ref;
   c1 = template[0];
   c2 = template[1];
   output = template + "";
   skipNext = false;
   inName = false;
   started = -1;
   name = "";
   for (i = 1, _ref = template.length - 1; 1 <= _ref ? i <= _ref : i >= _ref; 1 <= _ref ? i++ : i--) {
     c2 = template[i];
     if (!skipNext) {
       if (c1 === "{" && c2 === "{") {
         if (inName) {
           throw new Error("Invalid template, opened brackets in an already opened context");
         }
         name = "";
         started = i;
         skipNext = true;
         inName = true;
       } else if (c1 === "}" && c2 === "}") {
         if (!inName) {
           throw new Error("Invalid template, closed unopened brackets");
         }
         displayPart = name.indexOf(":");
         if (displayPart >= 0) {
           display = name.substr(0, displayPart);
           key = name.substr(displayPart + 1);
           if (key === "") key = "__all__";
           displayClass = this.id + "-display-" + display + "-" + key;
           output = output.replace("Template:" + name + "", "");
           displayObj = new window[display](key);
           if (!(this.displays[key] != null)) this.displays[key] = [];
           this.displays[key].push({
             displayClass: displayClass,
             displayObj: displayObj
           });
         } else {
           output = output.replace("Template:" + name + "", "");
         }
         skipNext = true;
         inName = false;
       } else {
         if (inName) name += c1;
       }
     } else {
       skipNext = false;
     }
     c1 = c2;
   }
   return output;
 };
 return Output;

})();

Display = (function() {

 function Display(key) {
   this.key = key;
   this.value = void 0;
 }
 Display.prototype.attach = function(id) {
   throw new Error("Not implemented");
 };
 Display.prototype.set = function(values) {
   return this.value = values[this.key];
 };
 Display.prototype.pad = function(str, width, padLeft) {
   var length;
   if (str == null) str = "";
   if (width == null) width = 5;
   if (padLeft == null) padLeft = false;
   str = str.toString();
   length = str.length;
   while (length < width) {
     if (padLeft) {
       str = " " + str;
     } else {
       str += " ";
     }
     length++;
   }
   return str;
 };
 Display.prototype.wrap = function(str, width) {
   var i, length, output;
   if (str == null) str = "";
   if (width == null) width = 40;
   str = str.toString();
   length = str.length;
   output = [];
   i = 0;
   while (length >= width) {
     output.push(str.substr(i, width));
     length -= width;
     i += width;
   }
   if (length > 0) output.push(str.substr(i));
   return output;
 };
 return Display;

})();

DNADisplay = (function(_super) {

 __extends(DNADisplay, _super);
 DNADisplay.i = 0;
 function DNADisplay(key) {
   var that,
     _this = this;
   DNADisplay.__super__.constructor.apply(this, arguments);
   this["class"] = "dnadisplay-" + DNADisplay.i++;
   this.objects = [];
   that = this;
this.dom = $("
").attr("class", this["class"]).append($("<label>").append($("<input>").attr("class", this["class"] + "-toggleLines").attr("type", "checkbox").attr("checked", "on").on("change", function() {
     if ($("." + _this["class"] + "-toggleLines").attr("checked")) {
       $("." + _this["class"] + "-lines").hide();
       $("." + _this["class"] + "-numberedLines").show();
     } else {
       $("." + _this["class"] + "-lines").show();
       $("." + _this["class"] + "-numberedLines").hide();
     }
     return;
})).append(" Line Numbers")).append($("
").attr("class", this["class"] + "-lines product").hide()).append($("<pre>").attr("class", this["class"] + "-numberedLines product"));
  }

  DNADisplay.prototype.attach = function(elem) {
    $(elem).append(this.dom);
    return this.objects = $("." + this["class"]);
  };

  DNADisplay.prototype.set = function(value) {
    var i, line, lines, numberWidth, numberedLines, outputLines, width, widthNumbered, _len;
    width = 60;
    numberWidth = 6;
    widthNumbered = 40;
    lines = this.wrap(value, width);
    numberedLines = this.wrap(value, widthNumbered);
    outputLines = [];
    for (i = 0, _len = numberedLines.length; i < _len; i++) {
      line = numberedLines[i];
      if (line.length > 0) {
        outputLines[i] = this.pad(i * widthNumbered + 1, numberWidth, true) + " - " + this.pad((i + 1) * widthNumbered, numberWidth) + "  " + line;
      }
    }
    this.full = lines.join("\n");
    this.fullNumbered = outputLines.join("\n");
    $("." + this["class"] + "-lines").text(this.full);
    return $("." + this["class"] + "-numberedLines").text(this.fullNumbered);
  };

  return DNADisplay;

})(Display);

RSDisplay = (function(_super) {

  __extends(RSDisplay, _super);

  RSDisplay.i = 0;

  function RSDisplay(key) {
    var that;
    RSDisplay.__super__.constructor.apply(this, arguments);
    this["class"] = "rsdisplay-" + RSDisplay.i++;
    this.objects = [];
    that = this;
    this.dom = $("<div>").attr("class", this["class"]).append($("<pre>").attr("class", this["class"] + "-pre"));
  }

  RSDisplay.prototype.attach = function(elem) {
    $(elem).append(this.dom);
    return this.objects = $("." + this["class"]);
  };

  RSDisplay.prototype.set = function(_arg) {
    var c, complement, full, left, leftCompStr, leftCut, leftRS, leftRSInput, leftStr, overhang, randomBases, right, rightCompStr, rightCut, rightRS, rightRSInput, rightStr, rs, text, type, _i, _j, _len, _len2, _ref, _ref2;
    rs = _arg.rs, type = _arg.type, leftRS = _arg.leftRS, rightRS = _arg.rightRS;
    if (!type.rs) {
      $("." + this["class"] + "-pre").html("Not applicable");
      return;
    }
    leftRSInput = leftRS.rs;
    rightRSInput = rightRS.rs;
    left = rs.left, right = rs.right, full = rs.full, randomBases = rs.randomBases;
    leftCut = DNA.getCutPosition(leftRSInput);
    if (leftCut < 0) {
      leftRS = DNA.fromString(left).findRS(true);
      leftCut = DNA.getCutPosition(leftRS);
    } else {
      leftRS = leftRSInput;
    }
    if (leftRS.length === 0) leftRS = "?";
    rightCut = DNA.getCutPosition(rightRSInput);
    if (rightCut < 0) {
      rightRS = DNA.fromString(right).findRS(true);
      rightCut = DNA.getCutPosition(rightRS);
    } else {
      rightRS = rightRSInput;
    }
    if (rightRS.length === 0) rightRS = "?";
    leftStr = full.substr(0, randomBases + left.length + 10);
    rightStr = full.substr(full.length - (randomBases + right.length + 10));
    if (randomBases > 6) {
      leftStr = "..." + leftStr.substr(randomBases - 3);
      rightStr = rightStr.substr(0, rightStr.length - (randomBases - 3)) + "...";
      randomBases = 6;
    }
    leftCompStr = "";
    rightCompStr = "";
    complement = function(c) {
      switch (c) {
        case "A":
          return "T";
        case "T":
          return "A";
        case "C":
          return "G";
        case "G":
          return "C";
        default:
          return c;
      }
    };
    _ref = leftStr.split("");
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      c = _ref[_i];
      leftCompStr += complement(c);
    }
    _ref2 = rightStr.split("");
    for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
      c = _ref2[_j];
      rightCompStr += complement(c);
    }
    if (leftCut >= 0) {
      overhang = Math.ceil(Math.abs(left.length / 2 - leftCut)) + 1;
      leftStr = leftStr.substr(0, randomBases + leftCut) + this.pad("", overhang + 1) + leftStr.substr(randomBases + leftCut);
      leftCompStr = leftCompStr.substr(0, randomBases + left.length - leftCut) + this.pad("", overhang + 1) + leftCompStr.substr(randomBases + left.length - leftCut);
    } else {
      leftStr = leftStr.substr(0, randomBases) + "  " + leftStr.substr(randomBases, left.length) + "  " + leftStr.substr(randomBases + left.length);
      leftCompStr = leftCompStr.substr(0, randomBases) + "  " + leftCompStr.substr(randomBases, left.length) + "  " + leftCompStr.substr(randomBases + left.length);
    }
    if (rightCut >= 0) {
      overhang = Math.ceil(Math.abs(right.length / 2 - rightCut)) + 1;
      rightStr = rightStr.substr(0, rightStr.length - (randomBases + right.length - rightCut)) + this.pad("", overhang + 1) + rightStr.substr(rightStr.length - (randomBases + right.length - rightCut));
      rightCompStr = rightCompStr.substr(0, rightCompStr.length - (randomBases + rightCut)) + this.pad("", overhang + 1) + rightCompStr.substr(rightCompStr.length - (randomBases + rightCut));
    } else {
      rightStr = rightStr.substr(0, rightStr.length - (randomBases + right.length)) + "  " + rightStr.substr(rightStr.length - (randomBases + right.length), right.length) + "  " + rightStr.substr(rightStr.length - randomBases);
      rightCompStr = rightCompStr.substr(0, rightCompStr.length - (randomBases + right.length)) + "  " + rightCompStr.substr(rightCompStr.length - (randomBases + right.length), right.length) + "  " + rightCompStr.substr(rightCompStr.length - randomBases);
    }
    text = [];
    text.push("Left restriction site: " + leftRS + (leftCut < 0 ? " (cutting site unknown)" : ""));
    text.push("Right restriction site: " + rightRS + (rightCut < 0 ? " (cutting site unknown)" : ""));
    text.push("");
    text.push("5'" + this.pad("3'", leftStr.length + rightStr.length + 1, true));
    text.push(leftStr + "..." + rightStr);
    text.push(leftCompStr + "..." + rightCompStr);
    text.push("3'" + this.pad("5'", leftStr.length + rightStr.length + 1, true));
    return $("." + this["class"] + "-pre").text(text.join("\n"));
  };

  return RSDisplay;

})(Display);

SelfDimerDisplay = (function(_super) {

  __extends(SelfDimerDisplay, _super);

  SelfDimerDisplay.i = 0;

  function SelfDimerDisplay(key) {
    var that,
      _this = this;
    SelfDimerDisplay.__super__.constructor.apply(this, arguments);
    this["class"] = "selfdimerdisplay-" + RSDisplay.i++;
    this.objects = [];
    that = this;
    this.dom = $("<div>").attr("class", this["class"]).append($("<span>").text("This calculation is quite heavy and is done on demand. Press on the button to start the calculations:")).append("<br>").append($("<input>").attr("type", "button").attr("value", "Calculate").attr("class", "btn").click(function() {
      return _this.click();
    })).append("<br>").append("<br>").append($("<pre>").attr("class", this["class"] + "-pre").html("Press the calculate button to update this data"));
  }

  SelfDimerDisplay.prototype.attach = function(elem) {
    $(elem).append(this.dom);
    return this.objects = $("." + this["class"] + "-pre");
  };

  SelfDimerDisplay.prototype.set = function(_arg) {
    var primer1, primer2;
    primer1 = _arg.primer1, primer2 = _arg.primer2;
    if (primer1 !== this.primer1 || primer2 !== this.primer2) {
      this.primer1 = primer1;
      this.primer2 = primer2;
      return this.objects.html("Press the calculate button to update this data");
    }
  };

  SelfDimerDisplay.prototype.click = function() {
    var report;
    report = this.generateReport("Primer 1", this.primer1) + "\n\n" + this.generateReport("Primer 2", this.primer2);
    return this.objects.html(report);
  };

  SelfDimerDisplay.prototype.generateReport = function(name, primer) {
    var character, folds, lineC, lineN, lines, matches, number, p, pairs, rna, _i, _j, _len, _ref, _ref2, _results;
    lines = [];
    lines.push(name + ":");
    rna = RNA.fromString(primer);
    _ref = rna.findKnots(), matches = _ref.matches, folds = _ref.folds;
    lines.push("Amount of matches: " + matches);
    pairs = _.zip((function() {
      _results = [];
      for (var _i = 1, _ref2 = primer.length; 1 <= _ref2 ? _i <= _ref2 : _i >= _ref2; 1 <= _ref2 ? _i++ : _i--){ _results.push(_i); }
      return _results;
    }).apply(this), primer.split(""));
    lineC = "";
    lineN = "";
    for (_j = 0, _len = pairs.length; _j < _len; _j++) {
      p = pairs[_j];
      number = p[0] + " ";
      character = this.pad(p[1], number.length - 1, true) + " ";
      if (lineC.length + number.length > 60) {
        lines.push(lineC);
        lines.push(lineN);
        lines.push("");
        lineC = "";
        lineN = "";
      }
      lineC += character;
      lineN += number;
    }
    if (lineC.length > 0) {
      lines.push(lineC);
      lines.push(lineN);
      lines.push("");
    }
    return lines.join("\n");
  };

  return SelfDimerDisplay;

})(Display);
var Primer;

Primer = (function() {

  Primer.minLength = 8;

  Primer.maxLength = 50;

  Primer.maxDiff = 8;

  Primer.lastData = "";

  Primer.lastTail1 = "";

  Primer.lastTail2 = "";

  Primer.lastTm = 0;

  Primer.lastRandEdgeSize = -1;

  Primer.reset = function() {
    this.lastData = "";
    this.lastTm = 0;
    this.lastTail1 = "";
    this.lastTail2 = "";
    return this.lastRandEdgeSize = -1;
  };

  function Primer(data) {
    if (data instanceof DNA) {
      this.seq = data.clone();
    } else {
      this.seq = DNA.fromString(data);
    }
    this.tail1 = new DNA([]);
    this.tail2 = new DNA([]);
    this.randEdgeSize = 0;
  }

  Primer.prototype.setTails = function(tail1, tail2) {
    this.tail1 = tail1;
    this.tail2 = tail2;
  };

  Primer.prototype.addRandomEdges = function(randEdgeSize) {
    this.randEdgeSize = randEdgeSize;
  };

  Primer.prototype.isSameAsLast = function(data, tm) {
    return Primer.lastTail1.toString() === this.tail1.toString() && Primer.lastTail2.toString() === this.tail2.toString() && Primer.lastData === DNA.fromString(data).toString() && Primer.lastTm === tm && Primer.lastRandEdgeSize === this.randEdgeSize;
  };

  Primer.prototype.generate = function(goal) {
    var diff, l1, l2, maxLength, primer1Length, primer1Temp, primer2Length, primer2Temp, rand1, rand2, revCompl, temp, _ref, _ref2;
    if (2 * Primer.minLength > this.seq.length()) {
      throw new Error("The given sequence is too short");
    }
    Primer.lastData = this.seq.toString();
    Primer.lastTm = goal;
    Primer.lastTail1 = this.tail1;
    Primer.lastTail2 = this.tail2;
    Primer.lastRandEdgeSize = this.randEdgeSize;
    maxLength = Math.min(Math.floor(this.seq.length() / 2), Primer.maxLength);
    revCompl = this.seq.reverseComplement();
    primer1Length = -1;
    primer1Temp = 0;
    primer2Length = -1;
    primer2Temp = 0;
    for (l1 = _ref = Primer.minLength; _ref <= maxLength ? l1 <= maxLength : l1 >= maxLength; _ref <= maxLength ? l1++ : l1--) {
      temp = this.seq.snip(l1).tm();
      diff = Math.abs(temp - goal);
      if (diff <= Primer.maxDiff && (primer1Length === -1 || Math.abs(primer1Temp - goal) > diff)) {
        primer1Length = l1;
        primer1Temp = temp;
      }
      if (temp >= goal) break;
    }
    for (l2 = _ref2 = Primer.minLength; _ref2 <= maxLength ? l2 <= maxLength : l2 >= maxLength; _ref2 <= maxLength ? l2++ : l2--) {
      temp = revCompl.snip(l2).tm();
      diff = Math.abs(temp - goal);
      if (diff <= Primer.maxDiff && (primer2Length === -1 || Math.abs(primer2Temp - goal) > diff)) {
        primer2Length = l2;
        primer2Temp = temp;
      }
      if (temp >= goal) break;
    }
    if (primer1Length < 0 || primer2Length < 0) {
      throw new Error("Couldn't find a primer of correct length that satisfies the conditions");
    }
    rand1 = DNA.fromRandom(this.randEdgeSize);
    rand2 = DNA.fromRandom(this.randEdgeSize);
    return {
      rs: {
        left: this.tail1.toString(),
        right: this.tail2.toString(),
        randomBases: this.randEdgeSize,
        full: rand1.toString() + this.tail1.toString() + this.seq.toString() + this.tail2.reverseComplement().toString() + rand2.reverseComplement().toString()
      },
      primer1: rand1.toString() + " " + this.tail1.toString() + " " + this.seq.snip(primer1Length).toString(),
      primers: {
        primer1: rand1.toString() + this.tail1.toString() + this.seq.snip(primer1Length).toString(),
        primer2: rand2.toString() + this.tail2.toString() + revCompl.snip(primer2Length).toString()
      },
      primer1NoTail: this.seq.snip(primer1Length).toString(),
      primer1Tm: primer1Temp,
      primer1Length: primer1Length,
      primer1TailLength: rand1.length() + this.tail1.length(),
      primer2: rand2.toString() + " " + this.tail2.toString() + " " + revCompl.snip(primer2Length).toString(),
      primer2NoTail: revCompl.snip(primer2Length).toString(),
      primer2Tm: primer2Temp,
      primer2Length: primer2Length,
      primer2TailLength: rand2.length() + this.tail2.length(),
      full: rand1.toString() + this.tail1.toString() + this.seq.toString() + this.tail2.reverseComplement().toString() + rand2.reverseComplement().toString()
    };
  };

  return Primer;

})();
var PrimerFinder;

PrimerFinder = (function() {

  function PrimerFinder(_arg) {
    this.output = _arg.output;
    this.defaults = {
      primer1: "No data",
      primer1Tm: "?",
      primer1Length: 0,
      primer2: "No data",
      primer2Tm: "?",
      primer2Length: 0,
      annealingTm: "?"
    };
  }

  PrimerFinder.prototype.calc = function(data, messages, error) {
    var k, match1, match2, p, primers, result, tail, tail1, tail2, v, validate, _i, _len, _ref, _ref2;
    if (error) {
      messages = ["A fatal error occured:"].concat(messages);
      this.output(data, messages);
      return;
    }
    _ref = this.defaults;
    for (k in _ref) {
      v = _ref[k];
      if (!(data[k] != null)) data[k] = v;
    }
    try {
      p = new Primer(data.dna);
      validate = function(primer1, primer2, dna) {};
      if (data.type.custom) p.setTails(data.leftTail, data.rightTail);
      if (data.type.rs) {
        match1 = data.leftRS.dna.findRS();
        match2 = data.rightRS.dna.findRS();
        if ((match1 != null) && match1.length > 0) {
          messages.push("The left restriction site is " + match1);
        } else {
          messages.push("The left restriction site wasn't recognized");
        }
        if ((match2 != null) && match2.length > 0) {
          messages.push("The right restriction site is " + match2);
        } else {
          messages.push("The right restriction site wasn't recognized");
        }
        p.setTails(data.leftRS.dna, data.rightRS.dna.reverseComplement());
        p.addRandomEdges(data.randomBases);
        messages.push("Click the 'Generate' button to regenerate the random bases added on the end of the primers.");
        messages.push("In the case of a plasmid where the piece between A and B is cut out, and A is used for the primer 1 RS (and B for the second).\nThe generated primers will put the RS such that the given DNA starts at the A side and ends at the B side (in short,\nit will probably work as you expected, but still check what happens with some other tool).");
      }
      if (data.type.known) {
        tail1 = "";
        tail2 = "";
        _ref2 = window.tails;
        for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
          tail = _ref2[_i];
          if (tail.value === data.standardTail) {
            tail1 = tail.primer1Tail;
            tail2 = tail.primer2Tail;
            if (tail.messages != null) messages = [].concat(tail.messages);
            if (tail.validate != null) validate = tail.validate;
          }
        }
        p.setTails(DNA.fromString(tail1), DNA.fromString(tail2));
      }
      primers = p.generate(data.tm);
      result = validate(primers.primer1NoTail, primers.primer2NoTail, data.dna);
      if (result != null) messages.push(result);
      for (k in primers) {
        v = primers[k];
        data[k] = v;
      }
      messages = messages;
    } catch (e) {
      messages = [e.message];
    }
    data.annealingTm = Math.min(data.primer1Tm, data.primer2Tm);
    if (Math.abs(data.primer1Tm - data.primer2Tm) > 5) {
      messages.push("The difference between the primer's Tm is more than 5\u00B0C");
    }
    if (data.annealingTm > 70) {
      messages.push("Consider using the two-step PCR protocol");
    }
    if (data.annealingTm > 72) {
      messages.push("Annealing temperature shouldn't exceed 72\u00B0C");
      data.annealingTm = 72;
    }
    if (data.annealingTm < 45) {
      messages.push("The annealing temperature should be more than 45\u00B0C");
    }
    return this.output(data, messages);
  };

  return PrimerFinder;

})();
var RNA;

RNA = (function() {

  RNA.fromDNA = function(dna) {
    return new RNA(dna.data.map(function(x) {
      switch (x) {
        case "T":
          return "U";
        default:
          return x;
      }
    }));
  };

  RNA.fromString = function(data) {
    return RNA.fromDNA(DNA.fromString(data));
  };

  /*
    Note: Just like for the DNA class, you probably didn't want to use the constructor. Use fromDNA or fromString instead!
  */

  function RNA(data) {
    this.data = data;
    this.n = this.data.length;
    this.knots = void 0;
  }

  RNA.prototype.P = function(i, j) {
    if ((this.data[i] === "A" && this.data[j] === "U") || (this.data[i] === "U" && this.data[j] === "C")) {
      return 1;
    } else if ((this.data[i] === "G" && this.data[j] === "C") || (this.data[i] === "C" && this.data[j] === "G")) {
      return 1;
    } else {
      return 0;
    }
  };

  RNA.prototype.isTooSelfComplementary = function() {
    var fold, folds, i, matches, seq, _i, _len, _ref, _ref2;
    _ref = this.findKnots(), matches = _ref.matches, folds = _ref.folds;
    for (_i = 0, _len = folds.length; _i < _len; _i++) {
      fold = folds[_i];
      seq = [];
      console.log(fold);
      for (i = 0, _ref2 = fold.length - 1; 0 <= _ref2 ? i <= _ref2 : i >= _ref2; 0 <= _ref2 ? i++ : i--) {
        if (fold[i] === "(") {
          if (i > 0 && fold[i - 1] === "(") {
            seq[seq.length - 1] += this.data[i];
          } else {
            seq[seq.length] = this.data[i];
          }
        }
      }
      console.log(seq);
    }
    return false;
  };

  RNA.prototype.findKnots = function(minSize) {
    var HM, M, a, a1, a2, createSolution, hashSet, hm, hs, i, j, k, l, m, max, output, p, sol, str, tmp, _i, _j, _k, _l, _len, _len2, _len3, _len4, _ref, _ref10, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9,
      _this = this;
    if (minSize == null) minSize = 2;
    if (this.knots !== void 0) return this.knots;
    createSolution = function() {
      var i, _ref, _results;
      _results = [];
      for (i = 1, _ref = _this.n; 1 <= _ref ? i <= _ref : i >= _ref; 1 <= _ref ? i++ : i--) {
        _results.push(".");
      }
      return _results;
    };
    HM = [];
    for (l = 0, _ref = this.n - 1; 0 <= _ref ? l <= _ref : l >= _ref; 0 <= _ref ? l++ : l--) {
      hm = [];
      for (m = 0, _ref2 = this.n - 1; 0 <= _ref2 ? m <= _ref2 : m >= _ref2; 0 <= _ref2 ? m++ : m--) {
        hm[m] = [createSolution()];
      }
      HM[l] = hm;
    }
    M = (function() {
      var _ref3, _results;
      _results = [];
      for (i = 1, _ref3 = this.n; 1 <= _ref3 ? i <= _ref3 : i >= _ref3; 1 <= _ref3 ? i++ : i--) {
        _results.push((function() {
          var _ref4, _results2;
          _results2 = [];
          for (j = 1, _ref4 = this.n; 1 <= _ref4 ? j <= _ref4 : j >= _ref4; 1 <= _ref4 ? j++ : j--) {
            _results2.push(0);
          }
          return _results2;
        }).call(this));
      }
      return _results;
    }).call(this);
    for (i = _ref3 = this.n - 1; _ref3 <= 0 ? i <= 0 : i >= 0; _ref3 <= 0 ? i++ : i--) {
      for (j = i, _ref4 = this.n - 1; i <= _ref4 ? j <= _ref4 : j >= _ref4; i <= _ref4 ? j++ : j--) {
        max = 0;
        if (i < j - minSize) {
          max = M[i + 1][j - 1] + this.P(i, j);
          hs = [];
          _ref5 = HM[i + 1][j - 1];
          for (_i = 0, _len = _ref5.length; _i < _len; _i++) {
            sol = _ref5[_i];
            if (this.P(i, j) === 1) {
              sol[i] = "(";
              sol[j] = ")";
            } else {
              sol[i] = ".";
              sol[j] = ".";
            }
            if (hs.length < 50) hs.push(sol);
          }
          for (k = i, _ref6 = j - 1; i <= _ref6 ? k <= _ref6 : k >= _ref6; i <= _ref6 ? k++ : k--) {
            tmp = M[i][k] + M[k + 1][j];
            if (tmp > max) {
              max = tmp;
              hs = [];
            }
            if (tmp >= max) {
              _ref7 = HM[i][k];
              for (_j = 0, _len2 = _ref7.length; _j < _len2; _j++) {
                a1 = _ref7[_j];
                _ref8 = HM[k + 1][j];
                for (_k = 0, _len3 = _ref8.length; _k < _len3; _k++) {
                  a2 = _ref8[_k];
                  a = createSolution(this.n);
                  for (p = 0; 0 <= k ? p <= k : p >= k; 0 <= k ? p++ : p--) {
                    a[p] = a1[p];
                  }
                  for (p = _ref9 = k + 1; _ref9 <= j ? p <= j : p >= j; _ref9 <= j ? p++ : p--) {
                    a[p] = a2[p];
                  }
                  if (hs.length < 50) hs.push(a);
                }
              }
            }
          }
          M[i][j] = max;
          HM[i][j] = hs;
        }
      }
    }
    output = [];
    hashSet = {};
    _ref10 = HM[0][this.n - 1];
    for (_l = 0, _len4 = _ref10.length; _l < _len4; _l++) {
      sol = _ref10[_l];
      str = sol.join("");
      if (hashSet[str] === void 0) {
        hashSet[str] = true;
        output.push(str);
      }
    }
    return this.knots = {
      matches: M[0][this.n - 1],
      folds: output
    };
  };

  return RNA;

})();
/*
Container class that helps with Tm calculations. It has been seperated to enable easy improvements to the
algorithm.
*/
var TmCalc;

TmCalc = (function() {

  function TmCalc() {}

  TmCalc.maxCacheSize = 500;

  TmCalc.cache = {};

  TmCalc.calc = function(data) {
    var key, keys, result;
    if (!this.cache[data]) {
      result = Math.round(this.calcFull(data) * 10) / 10;
      if (this.cache.length >= this.maxCacheSize) {
        keys = Object.keys(TmCalc.cache);
        key = keys[Math.floor(Math.random() * keys.length)];
        delete TmCalc.cache[key];
      }
      this.cache[data] = result;
    }
    return this.cache[data];
  };

  TmCalc.complement = function(n) {
    switch (n) {
      case "A":
        return "T";
      case "T":
        return "A";
      case "C":
        return "G";
      case "G":
        return "C";
      default:
        throw new Error("Invalid character encountered (" + n + ")");
    }
  };

  TmCalc.neighborH = {
    AA: 9.1,
    AT: 8.6,
    TA: 6.0,
    CA: 5.8,
    GT: 6.5,
    CT: 7.8,
    GA: 5.6,
    CG: 11.9,
    GC: 11.1,
    GG: 11.0
  };

  TmCalc.getH = function(n1, n2) {
    return this.get(this.neighborH, n1, n2);
  };

  TmCalc.neighborG = {
    AA: 1.9,
    AT: 1.5,
    TA: 0.9,
    CA: 1.9,
    GT: 1.3,
    CT: 1.6,
    GA: 1.6,
    CG: 3.6,
    GC: 3.1,
    GG: 3.1
  };

  TmCalc.getG = function(n1, n2) {
    return this.get(this.neighborG, n1, n2);
  };

  TmCalc.neighborS = {
    AA: 24.0,
    AT: 23.9,
    TA: 16.9,
    CA: 12.9,
    GT: 17.3,
    CT: 20.8,
    GA: 13.5,
    CG: 27.8,
    GC: 26.7,
    GG: 26.6
  };

  TmCalc.getS = function(n1, n2) {
    return this.get(this.neighborS, n1, n2);
  };

  TmCalc.get = function(searchVar, n1, n2) {
    switch (n1 + n2) {
      case "AA":
        return searchVar["AA"];
      case "TT":
        return searchVar["AA"];
      case "AT":
        return searchVar["AT"];
      case "TA":
        return searchVar["TA"];
      case "TT":
        return searchVar["TA"];
      case "CA":
        return searchVar["CA"];
      case "TG":
        return searchVar["CA"];
      case "GT":
        return searchVar["GT"];
      case "AC":
        return searchVar["GT"];
      case "CT":
        return searchVar["CT"];
      case "AG":
        return searchVar["CT"];
      case "GA":
        return searchVar["GA"];
      case "TC":
        return searchVar["GA"];
      case "CG":
        return searchVar["CG"];
      case "TC":
        return searchVar["CG"];
      case "GC":
        return searchVar["GC"];
      case "TC":
        return searchVar["GC"];
      case "GG":
        return searchVar["GG"];
      case "CC":
        return searchVar["GG"];
      default:
        throw new Error("No idea what happened... (tried getting " + n1 + n2 + ")");
    }
  };

  TmCalc.calcFull = function(data) {
    /* See (Breslauer et al. 1986): http://www.pnas.org/content/83/11/3746.full.pdf
    */
    var R, dh, ds, dsinit, i, n1, n2, naConc, predG, predH, predS, primerConc, primerc_local, _ref;
    predH = 0;
    predG = -(5 + 0.4);
    predS = 0;
    primerConc = 500 * 10e-3;
    naConc = 50 * 10e-3;
    R = 1.987;
    n1 = data[0];
    for (i = 1, _ref = data.length - 1; 1 <= _ref ? i <= _ref : i >= _ref; 1 <= _ref ? i++ : i--) {
      n2 = data[i];
      predH -= this.getH(n1, n2);
      predG -= this.getG(n1, n2);
      predS -= this.getS(n1, n2);
      n1 = n2;
    }
    dh = 1000 * predH;
    ds = predS;
    dsinit = -10.8;
    primerc_local = primerConc / 4 * 1e-6;
    return dh / (dsinit + ds + R * Math.log(primerc_local / 10)) - 273.15 + 16.6 * Math.log(naConc / 10) / Math.LN10;
  };

  /* Again, old
  @calcFull: (data, options) ->
    ##
    For more details see: http://www.basic.northwestern.edu/biotools/oligocalc.html
    ##
    freq = @calcFrequencies(data)
    
    if not options
      options = {}
    
    if not options.Na
      options.Na = 0.05
    
    GC = freq["G"] + freq["C"]
    AT = freq["A"] + freq["T"]
    N = data.length
    
    if N < 14
      AT*2 + GC*4 - 16.6*Math.log(0.050)/Math.log(10) + 16.6*Math.log(options.Na)/Math.log(10)
    else if N < 50 #18
      100.5 + (41 * GC/N) - (820/N) + 16.6*Math.log(options.Na)/Math.log(10)
    #else if N < 50
    #  81.5 + (41 * GC/N) - (500/N) + 16.6*Math.log(options.Na) - 0.62*options.F
    else
      79.8 + 18.5*Math.log(options.Na)/Math.log(10) + (58.4 * GC/N) + (11.8 * (GC/N) * (GC/N)) - (820/N) 
    
    
  
  @calcFrequencies: (data) ->
    freq = {A: 0, C: 0, G: 0, T: 0}
    data.forEach (x) ->
      freq[x]++
    freq
  */

  return TmCalc;

})();

/* Old version (not very accurate):
  @calc: (data) ->
    Math.round(@calcFull(data)*100)/100
  
  @calcFull: (data) ->
    freq = @calcFrequencies(data)
    if data.length < 14
      4 * (freq["G"] + freq["C"]) + 2 * (freq["A"] + freq["T"])
    else
      64.9 + 41 * (freq["G"] + freq["C"] - 16.4)/data.length
  
  @calcFrequencies: (data) ->
    freq = {A: 0, C: 0, G: 0, T: 0}
    data.forEach (x) ->
      freq[x]++
    freq
*/
// Underscore.js 1.3.3
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);