Team:EPF-Lausanne/PrimerDesignHelper.js

From 2012.igem.org

(Difference between revisions)
 
(15 intermediate revisions not shown)
Line 1: Line 1:
 +
/*
 +
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' &rarr; 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' &rarr; 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($("<hr>")).prepend($("<div>").attr("class", "control-group " + this.rowClass).append($("<label>").attr("class", "control-label").attr("for", "help-checkbox").html("Show help blocks")).append($("<div>").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($("<div>").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($("<div>").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 $("<div>").attr("id", "row-" + this.id).attr("class", "control-group " + this.rowClass).append(this.generateLabelDOM()).append($("<div>").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 $("<p>").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 $("<hr>").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 $("<div>").attr("class", "input-append").append(input).append($("<span>").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 $("<div>").attr("class", "input-prepend input-append").append($("<span>").attr("class", "add-on").text("5'")).append(this.generateInputElementDOM(this.id, "text", this.data.value, this.callback)).append($("<span>").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 $("<div>").attr("class", "input-prepend input-append").append($("<span>").attr("class", "add-on").text("5'")).append(this.generateInputElementDOM(this.id, "text", this.data.value, this.callback)).append($("<span>").attr("class", "add-on").text("3'")).append($("<span>").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 = $("<div>");
 +
    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 (<b>{{primer1Tm}}\u00B0C</b>, {{primer1TailLength}} + {{primer1Length}}bp): 5' &rarr; 3'<br />\n<pre class='primer'>{{primer1}}</pre>\nPrimer 2 (<b>{{primer2Tm}}\u00B0C</b>, {{primer2TailLength}} + {{primer2Length}}bp): 5' &rarr; 3'<br />\n<pre class='primer'>{{primer2}}</pre>\n<b>Annealing temperature:</b> {{annealingTm}}\u00B0C\n\n<br /><br />\n<h3>Messages</h3>\n{{_messages}}\n\n<br />\n<h3>Digestion result</h3>\n{{RSDisplay:}}\n<br />\n<h3>PCR Product</h3>\n{{DNADisplay:full}}\n<!-- <br />\n<h3>Self Dimer</h3>\n{{SelfDimerDisplay:primers}} -->");
 +
  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' &rarr; 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 "<b>The starting ATG seems to be absent.</b>";
 +
      end = primer2.substr(0, 3).toUpperCase();
 +
      if (end === "TTA" || end === "TCA" || end === "CTA") {
 +
        return "<b>Remove the stop codons from the sequence!</b>";
 +
      }
 +
      if (dna.containsRS(["EcoRI", "SpeI", "XbaI", "PstI", "NotI"])) {
 +
        return "<b>One of the following restriction sites is present (and shouldn't be): EcoRI, SpeI, XbaI, PstI, NotI</b>";
 +
      }
 +
      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 "<b>One of the following restriction sites is present (and shouldn't be): EcoRI, SpeI, XbaI, PstI, NotI</b>";
 +
      }
 +
      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("<ul><li>" + output.join("</li><li>") + "</li></ul>");
 +
  };
 +
 +
  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("{{" + name + "}}", "<span class='" + displayClass + "'></span>");
 +
            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("{{" + name + "}}", "<span class='" + this.id + "-match-" + name + "'></span>");
 +
          }
 +
          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 = $("<div>").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($("<pre>").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
Container class that helps with Tm calculations. It has been seperated to enable easy improvements to the
algorithm.
algorithm.
*/
*/
-
 
var TmCalc;
var TmCalc;
Line 9: Line 2,258:
   function TmCalc() {}
   function TmCalc() {}
 +
 +
  TmCalc.maxCacheSize = 500;
 +
 +
  TmCalc.cache = {};
   TmCalc.calc = function(data) {
   TmCalc.calc = function(data) {
-
     return Math.round(this.calcFull(data) * 10) / 10;
+
     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];
   };
   };
Line 128: Line 2,391:
     /* See (Breslauer et al. 1986): http://www.pnas.org/content/83/11/3746.full.pdf
     /* 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;
-
     var R, i, n1, n2, naConc, predG, predH, predS, primerConc, _i, _ref;
+
     predH = 0;
     predH = 0;
     predG = -(5 + 0.4);
     predG = -(5 + 0.4);
     predS = 0;
     predS = 0;
-
     primerConc = 500 * 10e-9;
+
     primerConc = 500 * 10e-3;
-
     naConc = 0.03;
+
     naConc = 50 * 10e-3;
     R = 1.987;
     R = 1.987;
     n1 = data[0];
     n1 = data[0];
-
     for (i = _i = 1, _ref = data.length - 1; 1 <= _ref ? _i <= _ref : _i >= _ref; i = 1 <= _ref ? ++_i : --_i) {
+
     for (i = 1, _ref = data.length - 1; 1 <= _ref ? i <= _ref : i >= _ref; 1 <= _ref ? i++ : i--) {
       n2 = data[i];
       n2 = data[i];
-
       predH += this.getH(n1, n2);
+
       predH -= this.getH(n1, n2);
-
       predG += this.getG(n1, n2);
+
       predG -= this.getG(n1, n2);
-
       predS += this.getS(n1, n2);
+
       predS -= this.getS(n1, n2);
       n1 = n2;
       n1 = n2;
     }
     }
-
     return (1000 * (predH - 3.4) / (predS + R * Math.log(1 / primerConc))) - 272.9 + 7.21 * Math.log(naConc / 1000) / Math.log(10);
+
     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;
   };
   };
Line 181: Line 2,447:
     freq
     freq
   */
   */
-
 
   return TmCalc;
   return TmCalc;
Line 204: Line 2,469:
     freq
     freq
*/
*/
-
 
+
// Underscore.js 1.3.3
-
/*
+
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
-
Immutable class that makes it possible to work with (very simplified) DNA sequences
+
// 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.
-
var Sequence,
+
// For all details and documentation:
-
  __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
// 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==
-
Sequence = (function() {
+
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,
-
  Sequence.allowed = ["A", "C", "G", "T"];
+
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,
-
  Sequence.fromString = function(data) {
+
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,
-
    var seq;
+
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=
-
    seq = new Sequence(data.toUpperCase().replace(/\s/ig, "").split(''));
+
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&&
-
    seq.validateData();
+
(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]};
-
    return seq;
+
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=
-
  Sequence.fromRandom = function(length) {
+
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<=
-
    var data, i;
+
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=
-
    data = (function() {
+
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=
-
      var _i, _results;
+
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));
-
      _results = [];
+
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])&&
-
      for (i = _i = 1; 1 <= length ? _i <= length : _i >= length; i = 1 <= length ? ++_i : --_i) {
+
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=
-
        _results.push(Sequence.allowed[Math.floor(Math.random() * Sequence.allowed.length)]);
+
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,
-
      return _results;
+
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};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||
-
    return new Sequence(data);
+
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);
-
  /*
+
-
    The constructor assumes the data has been validated (for performance reasons -> premature and non-consequent
+
-
    optimization ^^). Anyway, the Sequence.fromString function is probably what you were looking for!
+
-
  */
+
-
 
+
-
 
+
-
  function Sequence(data) {
+
-
    this.data = data;
+
-
  }
+
-
 
+
-
  Sequence.prototype.validateData = function() {
+
-
    if (this.data.some(function(x) {
+
-
      return __indexOf.call(Sequence.allowed, x) < 0;
+
-
    })) {
+
-
      throw new Error("Invalid character encountered");
+
-
    }
+
-
  };
+
-
 
+
-
  Sequence.prototype.length = function() {
+
-
    return this.data.length;
+
-
  };
+
-
 
+
-
  Sequence.prototype.snip = function(length) {
+
-
    if (length >= 0 && length <= this.data.length) {
+
-
      return new Sequence(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)");
+
-
    }
+
-
  };
+
-
 
+
-
  Sequence.prototype.reverseComplement = function() {
+
-
    var complement, k, l, revCompl, v, _i, _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 = _i = 0, _len = _ref.length; _i < _len; k = ++_i) {
+
-
      v = _ref[k];
+
-
      revCompl[l - k] = complement(v);
+
-
    }
+
-
    return new Sequence(revCompl);
+
-
  };
+
-
 
+
-
  Sequence.prototype.append = function(seqToAdd) {
+
-
    return new Sequence(this.data.concat(seqToAdd.data));
+
-
  };
+
-
 
+
-
  Sequence.prototype.tm = function() {
+
-
    return TmCalc.calc(this.data);
+
-
  };
+
-
 
+
-
  Sequence.prototype.toString = function() {
+
-
    return this.data.join("");
+
-
  };
+
-
 
+
-
  return Sequence;
+
-
 
+
-
})();
+
-
var Primer;
+
-
 
+
-
Primer = (function() {
+
-
 
+
-
  Primer.minLength = 8;
+
-
 
+
-
  Primer.maxLength = 50;
+
-
 
+
-
  Primer.maxDiff = 8;
+
-
 
+
-
  function Primer(data) {
+
-
    this.seq = Sequence.fromString(data);
+
-
    this.tail1 = new Sequence([]);
+
-
    this.tail2 = new Sequence([]);
+
-
  }
+
-
 
+
-
  Primer.prototype.setTails = function(tail1, tail2) {
+
-
    this.tail1 = tail1;
+
-
    this.tail2 = tail2;
+
-
  };
+
-
 
+
-
  Primer.prototype.generate = function(goal) {
+
-
    var diff, l1, l2, maxLength, primer1Length, primer1Temp, primer2Length, primer2Temp, revCompl, temp, _i, _j, _ref, _ref1;
+
-
    if (2 * Primer.minLength > this.seq.length()) {
+
-
      throw new Error("The given sequence is too short");
+
-
    }
+
-
    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 = _i = _ref = Primer.minLength; _ref <= maxLength ? _i <= maxLength : _i >= maxLength; l1 = _ref <= maxLength ? ++_i : --_i) {
+
-
      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 = _j = _ref1 = Primer.minLength; _ref1 <= maxLength ? _j <= maxLength : _j >= maxLength; l2 = _ref1 <= maxLength ? ++_j : --_j) {
+
-
      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");
+
-
    }
+
-
    return {
+
-
      primer1: this.tail1.toString() + " " + this.seq.snip(primer1Length).toString(),
+
-
      primer1NoTail: this.seq.snip(primer1Length).toString(),
+
-
      primer1Temp: primer1Temp,
+
-
      primer1bp: primer1Length,
+
-
      primer1Tailbp: this.tail1.length(),
+
-
      primer2: this.tail2.toString() + " " + revCompl.snip(primer2Length).toString(),
+
-
      primer2NoTail: revCompl.snip(primer2Length).toString(),
+
-
      primer2Temp: primer2Temp,
+
-
      primer2bp: primer2Length,
+
-
      primer2Tailbp: this.tail2.length()
+
-
    };
+
-
  };
+
-
 
+
-
  return Primer;
+
-
 
+
-
})();
+
-
 
+
-
 
+
-
 
+
-
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 °C",
+
-
"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){
+
-
var end = primer2.substr(0, 3).toUpperCase();
+
-
//        TAA            TGA            TAG
+
-
if(end == "TTA" || end == "TCA" || end == "CTA"){
+
-
return "<b>Remove the stop codons from the sequence!</b>"
+
-
}
+
-
}
+
-
},
+
-
{
+
-
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 °C",
+
-
"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>"
+
-
]
+
-
}
+
-
];
+
-
window.calc = function(){
+
-
var data = document.getElementById("data").value;
+
-
var isNormal = document.getElementById("primerTypeNormal").checked;
+
-
var isRS = document.getElementById("primerTypeRestrictionSites").checked;
+
-
var isKnown = document.getElementById("primerTypeKnownTail").checked;
+
-
var goalTm = document.getElementById("goalTm").value;
+
-
+
-
try{
+
-
var p = new Primer(data);
+
-
var validate = function(primer1, primer2){};
+
-
+
-
if(isRS){
+
-
p.setTails(Sequence.fromString(document.getElementById("primer1Tail").value), Sequence.fromString(document.getElementById("primer2Tail").value));
+
-
}
+
-
+
-
var messages = [];
+
-
if(isKnown){
+
-
var selected = $("#knownTailSelect").val();
+
-
+
-
var tail1 = "";
+
-
var tail2 = "";
+
-
+
-
for(var i = 0; i < tails.length; i++){
+
-
if(tails[i].value == selected){
+
-
tail1 = tails[i].primer1Tail;
+
-
tail2 = tails[i].primer2Tail;
+
-
if(tails[i].messages)
+
-
messages = [].concat(tails[i].messages);
+
-
if(tails[i].validate)
+
-
validate = tails[i].validate;
+
-
}
+
-
}
+
-
+
-
p.setTails(Sequence.fromString(tail1), Sequence.fromString(tail2));
+
-
}
+
-
+
-
var primers = p.generate(goalTm);
+
-
+
-
/*if(isRS){
+
-
var rand1 = Sequence.fromRandom($("#primerRandBases").val());
+
-
var rand2 = Sequence.fromRandom($("#primerRandBases").val());
+
-
primers.primer1 = rand1.toString()+" "+primers.primer1;
+
-
primers.primer2 = rand2.toString()+" "+primers.primer2;
+
-
primers.primer1Tailbp += rand1.length();
+
-
primers.primer2Tailbp += rand2.length();
+
-
}*/
+
-
+
-
var result = validate(primers.primer1NoTail, primers.primer2NoTail);
+
-
+
-
if(result)
+
-
messages.push(result);
+
-
+
-
if(messages.length > 0){
+
-
window.output({
+
-
primers: primers,
+
-
messages: messages
+
-
});
+
-
}else{
+
-
window.output({
+
-
primers: primers
+
-
});
+
-
}
+
-
}catch(e){
+
-
window.output({
+
-
error: e.message
+
-
});
+
-
}
+
-
};
+
-
window.output = function(options){
+
-
var output = {
+
-
message: [],
+
-
primer1: "No data",
+
-
primer1Temp: "NaN",
+
-
primer1bp: "NaN",
+
-
primer1Tailbp: "NaN",
+
-
primer2: "No data",
+
-
primer2Temp: "NaN",
+
-
primer2bp: "NaN",
+
-
primer2Tailbp: "NaN"
+
-
};
+
-
if(options.error){
+
-
output.message.push(options.error);
+
-
}
+
-
if(options.messages){
+
-
output.message = output.message.concat(options.messages);
+
-
}
+
-
if(options.primers){
+
-
for(var key in options.primers){
+
-
if(key == "message"){
+
-
output[key].push(options.primers[key]);
+
-
}else{
+
-
output[key] = options.primers[key];
+
-
}
+
-
}
+
-
}
+
-
+
-
if(Math.abs(output.primer1Temp - output.primer2Temp) > 5){
+
-
output.message.push("The difference is more than 5 °C");
+
-
}
+
-
+
-
if(Math.min(output.primer1Temp, output.primer2Temp) > 72){
+
-
output.message.push("Consider using the two-step PCR protocol");
+
-
}
+
-
+
-
if(Math.min(output.primer1Temp, output.primer2Temp) < 45){
+
-
output.message.push("The annealing temperature should be more than 45 °C");
+
-
}
+
-
+
-
document.getElementById("messages").innerHTML = output.message.length > 0 ? "<ul><li>"+output.message.join("</li><li>")+"</li></ul>" : "";
+
-
document.getElementById("primer1").innerHTML = output.primer1;
+
-
document.getElementById("primer1Temp").innerHTML = output.primer1Temp;
+
-
document.getElementById("primer1bp").innerHTML = output.primer1Tailbp != 0 && output.primer1Tailbp != "NaN" ? output.primer1Tailbp+" + "+output.primer1bp : output.primer1bp;
+
-
document.getElementById("primer2").innerHTML = output.primer2;
+
-
document.getElementById("primer2Temp").innerHTML = output.primer2Temp;
+
-
document.getElementById("primer2bp").innerHTML = output.primer2Tailbp != 0 && output.primer2Tailbp != "NaN" ? output.primer2Tailbp+" + "+output.primer2bp : output.primer2bp;
+
-
document.getElementById("annealingTemp").innerHTML = Math.min(output.primer1Temp, output.primer2Temp);
+
-
};
+
-
 
+
-
window.updateRadioElements = function(){
+
-
if(document.getElementById('primerTypeRestrictionSites').checked){
+
-
$('#restrictionSiteRow').show();
+
-
}else{
+
-
$('#restrictionSiteRow').hide();
+
-
}
+
-
if(document.getElementById('primerTypeKnownTail').checked){
+
-
$('#knownTailRow').show();
+
-
}else{
+
-
$('#knownTailRow').hide();
+
-
}
+
-
};
+

Latest revision as of 11:14, 28 July 2012

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