<commit_before>'use strict';

var opts = {maxSockets: 25};
var Agent = require('http').Agent;
var neo4j = require('neo4j');

module.exports = {
  name: 'Neo4J',
  CONCURRENCY: 32,

  startup: function (host, cb) {
    var db = new neo4j.GraphDatabase({
      url: 'http://neo4j:abc@' + host + ':7474',
      agent: new Agent(opts)});

    cb(db);
  },

 warmup: function (db, cb) {
    db.cypher({query: 'MATCH (:PROFILES)--() return count(*) as count'},
      function (err, result) {
        if (err) return cb(err);

        console.log('INFO warmup done, relationships ' + result.count);

        cb(null);
      }
    );
  },

  getCollection: function (db, name, cb) {
    cb(null, name.toUpperCase());
  },

  dropCollection: function (db, name, cb) {
    name = name.toUpperCase();

    db.cypher({query: 'MATCH (n:' + name + ') DELETE n'}, cb);
  },

  createCollectionSync: function (db, name, cb) {
    cb();
  },

  getDocument: function (db, coll, id, cb) {
    db.cypher({query: 'MATCH (f:' + coll + ' {_key:{key}}) RETURN f',
               params: {key: id},
               headers: {Connection: 'keep-alive'},
               lean: true}, cb);
  },

  saveDocumentSync: function (db, coll, doc, cb) {
    db.cypher({query: 'CREATE (f:' + coll + ' {doc})',
               params: {doc: doc},
               headers: {Connection: 'keep-alive'},
               lean: true}, cb);
  },

  aggregate: function (db, coll, cb) {
    db.cypher({query: 'MATCH (f:' + coll + ') RETURN f.AGE, count(*)',
               headers: {Connection: 'keep-alive'},
               lean: true},

              function (err, result) {
                if (err) return cb(err);

                cb(null, result.length);
              });
  },

  neighbors: function (db, collP, collR, id, i, cb) {
    db.cypher({query: 'MATCH (s:' + collP + ' {_key:{key}})-->(n:' + collP + ') RETURN n._key',
               params: {key: id},
               headers: {Connection: 'keep-alive'},
               lean: true},

              function (err, result) {
                if (err) return cb(err);

                if (result.length === undefined) cb(null, 1);
                else cb(null, result.length);
              });
  },

  neighbors2: function (db, collP, collR, id, i, cb) {
    db.cypher({query: 'MATCH (s:' + collP + ' {_key:{key}})-[*1..2]->(n:'
                      + collP + ') RETURN DISTINCT n._key',
               params: {key: id},
               headers: {Connection: 'keep-alive'},
               lean: true},

              function (err, result) {
                if (err) return cb(err);

                if (result.map === undefined) {
                  result = [result['n._key']];
                }
                else {
                  result = result.map(function (x) { return x['n._key']; });
                }

                if (result.indexOf(id) === -1) {
                  cb(null, result.length);
                }
                else {
                  cb(null, result.length - 1);
                }
              });
  },

  shortestPath: function (db, collP, collR, path, i, cb) {
    db.cypher({query: 'MATCH (s:' + collP + ' {_key:{from}}),(t:'
                      + collP + ' {_key:{to}}), p = shortestPath((s)-[*..15]->(t)) RETURN [x in nodes(p) | x._key] as path',
               params: {from: path.from, to: path.to},
               headers: {Connection: 'keep-alive'},
               lean: true},

              function (err, result) {
                if (err) return cb(err);

                if (result.length === 0) {cb(null, 0);}
                else {cb(null, (result[0].path.length - 1));}
              });
  }
};
<commit_msg>added neighbors2data<commit_after>'use strict';

var opts = {maxSockets: 25};
var Agent = require('http').Agent;
var Neo4j = require('neo4j');

module.exports = {
  name: 'Neo4J',
  CONCURRENCY: 32,

  startup: function (host, cb) {
    var db = new Neo4j.GraphDatabase({
      url: 'http://neo4j:abc@' + host + ':7474',
      agent: new Agent(opts)
    });

    cb(db);
  },

 warmup: function (db, cb) {
    db.cypher({query: 'MATCH (:PROFILES)--() return count(*) as count'},
      function (err, result) {
        if (err) return cb(err);

        console.log('INFO warmup done, relationships ' + result.count);

        cb(null);
      }
    );
  },

  getCollection: function (db, name, cb) {
    cb(null, name.toUpperCase());
  },

  dropCollection: function (db, name, cb) {
    name = name.toUpperCase();

    db.cypher({query: 'MATCH (n:' + name + ') DELETE n'}, cb);
  },

  createCollectionSync: function (db, name, cb) {
    cb();
  },

  getDocument: function (db, coll, id, cb) {
    db.cypher({query: 'MATCH (f:' + coll + ' {_key:{key}}) RETURN f',
               params: {key: id},
               headers: {Connection: 'keep-alive'},
               lean: true}, cb);
  },

  saveDocumentSync: function (db, coll, doc, cb) {
    db.cypher({query: 'CREATE (f:' + coll + ' {doc})',
               params: {doc: doc},
               headers: {Connection: 'keep-alive'},
               lean: true}, cb);
  },

  aggregate: function (db, coll, cb) {
    db.cypher({query: 'MATCH (f:' + coll + ') RETURN f.AGE, count(*)',
               headers: {Connection: 'keep-alive'},
               lean: true},

              function (err, result) {
                if (err) return cb(err);

                cb(null, result.length);
              });
  },

  neighbors: function (db, collP, collR, id, i, cb) {
    db.cypher({query: 'MATCH (s:' + collP + ' {_key:{key}})-->(n:' + collP + ') RETURN n._key',
               params: {key: id},
               headers: {Connection: 'keep-alive'},
               lean: true},

              function (err, result) {
                if (err) return cb(err);

                if (result.length === undefined) cb(null, 1);
                else cb(null, result.length);
              });
  },

  neighbors2: function (db, collP, collR, id, i, cb) {
    db.cypher({query: 'MATCH (s:' + collP + ' {_key:{key}})-[*1..2]->(n:'
                      + collP + ') RETURN DISTINCT n._key',
               params: {key: id},
               headers: {Connection: 'keep-alive'},
               lean: true},

              function (err, result) {
                if (err) return cb(err);

                if (result.map === undefined) {
                  result = [result['n._key']];
                }
                else {
                  result = result.map(function (x) { return x['n._key']; });
                }

                if (result.indexOf(id) === -1) {
                  cb(null, result.length);
                }
                else {
                  cb(null, result.length - 1);
                }
              });
  },

  neighbors2data: function (db, collP, collR, id, i, cb) {
    db.cypher({query: 'MATCH (s:' + collP + ' {_key:{key}})-[*1..2]->(n:'
                      + collP + ') RETURN DISTINCT n._key, n',
               params: {key: id},
               headers: {Connection: 'keep-alive'},
               lean: true},

              function (err, result) {
                if (err) return cb(err);

                if (result.map === undefined) {
                  result = [result['n._key']];
                }
                else {
                  result = result.map(function (x) { return x['n._key']; });
                }

                if (result.indexOf(id) === -1) {
                  cb(null, result.length);
                }
                else {
                  cb(null, result.length - 1);
                }
              });
  },

  shortestPath: function (db, collP, collR, path, i, cb) {
    db.cypher({query: 'MATCH (s:' + collP + ' {_key:{from}}),(t:'
                      + collP + ' {_key:{to}}), p = shortestPath((s)-[*..15]->(t)) RETURN [x in nodes(p) | x._key] as path',
               params: {from: path.from, to: path.to},
               headers: {Connection: 'keep-alive'},
               lean: true},

              function (err, result) {
                if (err) return cb(err);

                if (result.length === 0) {cb(null, 0);}
                else {cb(null, (result[0].path.length - 1));}
              });
  }
};
<|endoftext|>


<commit_before>// ==========================================================================
//                            DG.Calculator
//
//  Copyright (c) 2014 by The Concord Consortium, Inc. All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

sc_require('utilities/formatter');

/** @class

  This is a self-contained view that provides calculator functionality. It has
 a fixed height and width, awaiting only a top and left for its specification.

 @extends SC.View
 */
DG.Calculator = SC.View.extend( (function() // closure
/** @scope DG.Calculator.prototype */ {

  // Private closure variables
  var kButtonWidth = 30,
    kButtonHeight = 24,

    kMargin = 3,
    kTopMargin = 10,
    kGap = 2,

    kDoubleButtonWidth = 2 * kButtonWidth + kGap,

    kColumnWidth = kButtonWidth + kGap,
    kRowHeight = kButtonHeight + kGap,

    kColumnCount = 4,
    kRowCount = 5;

  return {  // return from closure

    classNames: ['calculator'],
    backgroundColor: '#333333',

    _justEvaled: NO,  // If the expressions has just been evaluated, pressing
    // number key should wipe it out while arithmetic operation should insert.

    // The view format is the default number format, which after some earlier
    // modification of the Protovis code uses the Unicode minus sign for negative numbers.
    viewFormat: DG.Format.number().fractionDigits( 0, 9).group(""),

    // The export format uses the ASCII minus sign for negative numbers,
    // for use in log statements, etc.
    exportFormat: DG.Format.number().fractionDigits( 0, 9).group("").negativeAffix("-",""),

    /**
      Formats values for use by the Calculator view.
      Uses the Unicode minus sign for negative numbers.
     */
    formatForView: function( iValue) {
      if( typeof iValue !== 'number') return String( iValue);
      // DG.Format has been modified separately to use the Unicode minus sign,
      // so this will format negative numbers with the Unicode minus sign.
      return this.viewFormat( iValue);
    },

    /**
      Formats values for export (e.g. for logging).
      Uses the ASCII minus sign for negative numbers.
     */
    formatForExport: function( iValue) {
      if( typeof iValue !== 'number') return iValue;
      // The export format uses the ASCII minus sign for negative numbers.
      return this.exportFormat( iValue);
    },

    clearValue: function() {
      this.editView.set( 'value', '' );
      this._justEvaled = NO;
    },
    insert: function( iToInsert ) {
      if( this._justEvaled && !isNaN( parseInt( iToInsert, 10 ) ) )
        this.clearValue();
      this.editView.replaceSelectionWithString( iToInsert );
      if( !this.editView.get('isFirstResponder'))
        this.editView.becomeFirstResponder();
      this._justEvaled = NO;
    },
    insertSymbol: function( aSymbol ) {
      // Typically parentheses or decimal point
      if( this._justEvaled )
        this.clearValue();
      this.insert( aSymbol );
    },
    calculate: function() {
      var tCurrent = this.editView.get( 'formulaExpression' ),
          tFormula = DG.Formula.create({ source: tCurrent }),
          tValue = '';

      // Don't try to evaluate error messages
      if( !SC.empty( tCurrent) && (tCurrent.charAt( 0) === '#'))
        return;

      try {
        tValue = tFormula.evaluateDirect();
      }
      catch( err ) {
        tValue = !SC.empty( err.message) ? err.message : String( err);

        // Put a '#' in front of errors so that they stand out and also so that
        // if the user presses return, we can detect that we shouldn't evaluate.
        tValue = '#' + tValue;
      }

      this.editView.set('value', this.formatForView( tValue));
      this._justEvaled = YES;
      DG.logUser( "Calculation done: %@ = %@", tCurrent, this.formatForExport( tValue));
    },

    init: function() {
      sc_super();

      this.setupSubViews();
    },

    /**
     * All buttons and the edit view are setup here.
     */
    setupSubViews: function() {
      var this_ = this;

      // The two views that require special treatment are the editView (because it's not a button)
      // and the equals button (because it is bigger than the others).
      this.editView = DG.FormulaTextEditView.create( {
        layout: { top: 5, left: 5, right: 5, height: 20 },
        value: '',
        continuouslyUpdatesValue: false,
        borderStyle: SC.BORDER_BEZEL,
        keyDown: function( evt) {
          if( this_.handleKeyDown( evt))
            return sc_super();
          else
            return true;  // prevent further handling
        }
      } );
      this.appendChild( this.editView );

      this.set('defaultFirstResponder', this.editView);

      this.equals = SC.ButtonView.create( {
        layout: { bottom: kMargin, left: kMargin + 2 * kColumnWidth,
                  width: kDoubleButtonWidth, height: kButtonHeight },
        title: '=',
        titleMinWidth: 0
      } );
      this.appendChild( this.equals );

      // We assign equal view its action here because then we don't require reference to globals
      this.equals.action = function() {
        this_.calculate();
      };

      // Fix our width and height in terms of the number of rows and columns and the
      // size of the buttons.
      var kTop = kTopMargin + kMargin + this.editView.layout.height;
      this.set( 'layout', { top: 0, left: 0,
          width: 4 * kMargin + kColumnCount * kColumnWidth,
          height: kTop + kRowCount * kRowHeight + kMargin }
      );

      // All but two child views we can lay out automatically. We only need to provide
      // a title and an action.
      // Note that we are using a "deprecated" form of installing actions in button views.
      // But it is clearly better for our purposes than separate action/target recommended
      // by SproutCore.
      var tParams = [
        [ 'C', function() {
          this_.clearValue();
        } ],
        [ '(', function() {
          this_.insertSymbol( '(' );
        } ],
        [ ')', function() {
          this_.insertSymbol( ')' );
        } ],
        [ '/', function() {
          this_.insert( ' / ' );
        } ],
        [ '7', function() {
          this_.insert( '7' );
        } ],
        [ '8', function() {
          this_.insert( '8' );
        } ],
        [ '9', function() {
          this_.insert( '9' );
        } ],
        [ 'X', function() {
          this_.insert( ' * ' );
        } ],
        [ '4', function() {
          this_.insert( '4' );
        } ],
        [ '5', function() {
          this_.insert( '5' );
        } ],
        [ '6', function() {
          this_.insert( '6' );
        } ],
        [ DG.UNICODE.MINUS_SIGN, function() {
          this_.insert(' ' + DG.UNICODE.MINUS_SIGN + ' ');
        } ],
        [ '1', function() {
          this_.insert( '1' );
        } ],
        [ '2', function() {
          this_.insert( '2' );
        } ],
        [ '3', function() {
          this_.insert( '3' );
        } ],
        [ '+', function() {
          this_.insert( ' + ' );
        } ],
        [ '0', function() {
          this_.insert( '0' );
        } ],
        [ '.', function() {
          this_.insertSymbol( '.' );
        } ]
      ];
      var tCounter = 0,
        kNumRows = 5;
      tParams.forEach( function( aParam ) {
        var tRow = Math.floor( tCounter / kColumnCount );
        var tColumn = tCounter % kColumnCount;
        var tButton = SC.ButtonView.create( {
          layout: { bottom: kMargin + (kNumRows - tRow - 1) * kRowHeight,
            left: kMargin + tColumn * kColumnWidth,
            width: kButtonWidth, height: kButtonHeight
          },
          title: aParam[0],
          action: aParam[1],
          titleMinWidth: 0
        } );
        this_.appendChild( tButton );
        tCounter++;
      } );
    },

    handleKeyDown: function( evt ) {
      // Weird bug: It is possible to get here after closing the calculator and then switching
      // to another component with the world not looking the way we expect. If so, just bail.
      if( !this ) return NO;
      if( (evt.which === SC.Event.KEY_RETURN) && !evt.isIMEInput) {
        // Some browsers send both keydown & keypress for the RETURN key, and SproutCore
        // forwards both to this handler, so we have to make sure we only handle one.
        // Since other browsers only send keydown and not keypress, we only respond to keydown.
        if( evt.type === 'keydown')
          this.calculate();
      }
      else if( evt.which === SC.Event.KEY_ESC) // <esc>
        this.clearValue();
      else {
        var tChar = String.fromCharCode( evt.which );
        if( this._justEvaled && ("/*-+\t".indexOf( tChar ) === -1) ) {
          this.clearValue();
        }
        else if( tChar === '=' ) {
          this.calculate();
          // We calculated, so we don't want sc_super to do its thing
          return false;
        }
        else
          this._justEvaled = NO;
      }
      return true;
    }

  };  // end return from closure
}()) );  // end closure
<commit_msg>Undo/Redo: Calculator<commit_after>// ==========================================================================
//                            DG.Calculator
//
//  Copyright (c) 2014 by The Concord Consortium, Inc. All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

sc_require('utilities/formatter');

/** @class

  This is a self-contained view that provides calculator functionality. It has
 a fixed height and width, awaiting only a top and left for its specification.

 @extends SC.View
 */
DG.Calculator = SC.View.extend( (function() // closure
/** @scope DG.Calculator.prototype */ {

  // Private closure variables
  var kButtonWidth = 30,
    kButtonHeight = 24,

    kMargin = 3,
    kTopMargin = 10,
    kGap = 2,

    kDoubleButtonWidth = 2 * kButtonWidth + kGap,

    kColumnWidth = kButtonWidth + kGap,
    kRowHeight = kButtonHeight + kGap,

    kColumnCount = 4,
    kRowCount = 5;

  return {  // return from closure

    classNames: ['calculator'],
    backgroundColor: '#333333',

    _justEvaled: NO,  // If the expressions has just been evaluated, pressing
    // number key should wipe it out while arithmetic operation should insert.

    // The view format is the default number format, which after some earlier
    // modification of the Protovis code uses the Unicode minus sign for negative numbers.
    viewFormat: DG.Format.number().fractionDigits( 0, 9).group(""),

    // The export format uses the ASCII minus sign for negative numbers,
    // for use in log statements, etc.
    exportFormat: DG.Format.number().fractionDigits( 0, 9).group("").negativeAffix("-",""),

    /**
      Formats values for use by the Calculator view.
      Uses the Unicode minus sign for negative numbers.
     */
    formatForView: function( iValue) {
      if( typeof iValue !== 'number') return String( iValue);
      // DG.Format has been modified separately to use the Unicode minus sign,
      // so this will format negative numbers with the Unicode minus sign.
      return this.viewFormat( iValue);
    },

    /**
      Formats values for export (e.g. for logging).
      Uses the ASCII minus sign for negative numbers.
     */
    formatForExport: function( iValue) {
      if( typeof iValue !== 'number') return iValue;
      // The export format uses the ASCII minus sign for negative numbers.
      return this.exportFormat( iValue);
    },

    clearValue: function() {
      var calculator = this,
          currentValue = this.editView.get('value');
      // Calculator is a singleton view, so it's ok to access the view directly from the Command
      DG.UndoHistory.execute(DG.Command.create({
        name: 'calculator.calculate',
        undoString: 'DG.Undo.calculator.calculate',
        redoString: 'DG.Redo.calculator.calculate',
        log: "Calculator value cleared",
        _redoValue: null,
        execute: function() {
          calculator.editView.set('value', this._redoValue || '');
          calculator._justEvaled = NO;
        },
        undo: function() {
          this._redoValue = calculator.editView.get('value');
          calculator.editView.set('value', currentValue);
          calculator._justEvaled = NO;
        }
      }));
    },
    insert: function( iToInsert ) {
      if( this._justEvaled && !isNaN( parseInt( iToInsert, 10 ) ) )
        this.clearValue();
      this.editView.replaceSelectionWithString( iToInsert );
      if( !this.editView.get('isFirstResponder'))
        this.editView.becomeFirstResponder();
      this._justEvaled = NO;
    },
    insertSymbol: function( aSymbol ) {
      // Typically parentheses or decimal point
      if( this._justEvaled )
        this.clearValue();
      this.insert( aSymbol );
    },
    calculate: function() {
      var tCurrent = this.editView.get( 'formulaExpression' ),
          tFormula = DG.Formula.create({ source: tCurrent }),
          tValue = '';

      // Don't try to evaluate error messages
      if( !SC.empty( tCurrent) && (tCurrent.charAt( 0) === '#'))
        return;

      try {
        tValue = tFormula.evaluateDirect();
      }
      catch( err ) {
        tValue = !SC.empty( err.message) ? err.message : String( err);

        // Put a '#' in front of errors so that they stand out and also so that
        // if the user presses return, we can detect that we shouldn't evaluate.
        tValue = '#' + tValue;
      }

      var newValue = this.formatForView( tValue),
          currentValue = this.editView.get('value'),
          calculator = this;
      // Calculator is a singleton view, so it's ok to access the view directly from the Command
      DG.UndoHistory.execute(DG.Command.create({
        name: 'calculator.calculate',
        undoString: 'DG.Undo.calculator.calculate',
        redoString: 'DG.Redo.calculator.calculate',
        log: "Calculation done: %@ = %@".fmt(tCurrent, this.formatForExport( tValue)),
        execute: function() {
          calculator.editView.set('value', newValue);
          calculator._justEvaled = YES;
        },
        undo: function() {
          calculator.editView.set('value', currentValue);
          calculator._justEvaled = NO;
        }
      }));
    },

    init: function() {
      sc_super();

      this.setupSubViews();
    },

    /**
     * All buttons and the edit view are setup here.
     */
    setupSubViews: function() {
      var this_ = this;

      // The two views that require special treatment are the editView (because it's not a button)
      // and the equals button (because it is bigger than the others).
      this.editView = DG.FormulaTextEditView.create( {
        layout: { top: 5, left: 5, right: 5, height: 20 },
        value: '',
        continuouslyUpdatesValue: false,
        borderStyle: SC.BORDER_BEZEL,
        keyDown: function( evt) {
          if( this_.handleKeyDown( evt))
            return sc_super();
          else
            return true;  // prevent further handling
        }
      } );
      this.appendChild( this.editView );

      this.set('defaultFirstResponder', this.editView);

      this.equals = SC.ButtonView.create( {
        layout: { bottom: kMargin, left: kMargin + 2 * kColumnWidth,
                  width: kDoubleButtonWidth, height: kButtonHeight },
        title: '=',
        titleMinWidth: 0
      } );
      this.appendChild( this.equals );

      // We assign equal view its action here because then we don't require reference to globals
      this.equals.action = function() {
        this_.calculate();
      };

      // Fix our width and height in terms of the number of rows and columns and the
      // size of the buttons.
      var kTop = kTopMargin + kMargin + this.editView.layout.height;
      this.set( 'layout', { top: 0, left: 0,
          width: 4 * kMargin + kColumnCount * kColumnWidth,
          height: kTop + kRowCount * kRowHeight + kMargin }
      );

      // All but two child views we can lay out automatically. We only need to provide
      // a title and an action.
      // Note that we are using a "deprecated" form of installing actions in button views.
      // But it is clearly better for our purposes than separate action/target recommended
      // by SproutCore.
      var tParams = [
        [ 'C', function() {
          this_.clearValue();
        } ],
        [ '(', function() {
          this_.insertSymbol( '(' );
        } ],
        [ ')', function() {
          this_.insertSymbol( ')' );
        } ],
        [ '/', function() {
          this_.insert( ' / ' );
        } ],
        [ '7', function() {
          this_.insert( '7' );
        } ],
        [ '8', function() {
          this_.insert( '8' );
        } ],
        [ '9', function() {
          this_.insert( '9' );
        } ],
        [ 'X', function() {
          this_.insert( ' * ' );
        } ],
        [ '4', function() {
          this_.insert( '4' );
        } ],
        [ '5', function() {
          this_.insert( '5' );
        } ],
        [ '6', function() {
          this_.insert( '6' );
        } ],
        [ DG.UNICODE.MINUS_SIGN, function() {
          this_.insert(' ' + DG.UNICODE.MINUS_SIGN + ' ');
        } ],
        [ '1', function() {
          this_.insert( '1' );
        } ],
        [ '2', function() {
          this_.insert( '2' );
        } ],
        [ '3', function() {
          this_.insert( '3' );
        } ],
        [ '+', function() {
          this_.insert( ' + ' );
        } ],
        [ '0', function() {
          this_.insert( '0' );
        } ],
        [ '.', function() {
          this_.insertSymbol( '.' );
        } ]
      ];
      var tCounter = 0,
        kNumRows = 5;
      tParams.forEach( function( aParam ) {
        var tRow = Math.floor( tCounter / kColumnCount );
        var tColumn = tCounter % kColumnCount;
        var tButton = SC.ButtonView.create( {
          layout: { bottom: kMargin + (kNumRows - tRow - 1) * kRowHeight,
            left: kMargin + tColumn * kColumnWidth,
            width: kButtonWidth, height: kButtonHeight
          },
          title: aParam[0],
          action: aParam[1],
          titleMinWidth: 0
        } );
        this_.appendChild( tButton );
        tCounter++;
      } );
    },

    handleKeyDown: function( evt ) {
      // Weird bug: It is possible to get here after closing the calculator and then switching
      // to another component with the world not looking the way we expect. If so, just bail.
      if( !this ) return NO;
      if( (evt.which === SC.Event.KEY_RETURN) && !evt.isIMEInput) {
        // Some browsers send both keydown & keypress for the RETURN key, and SproutCore
        // forwards both to this handler, so we have to make sure we only handle one.
        // Since other browsers only send keydown and not keypress, we only respond to keydown.
        if( evt.type === 'keydown')
          this.calculate();
      }
      else if( evt.which === SC.Event.KEY_ESC) // <esc>
        this.clearValue();
      else {
        var tChar = String.fromCharCode( evt.which );
        if( this._justEvaled && ("/*-+\t".indexOf( tChar ) === -1) ) {
          this.clearValue();
        }
        else if( tChar === '=' ) {
          this.calculate();
          // We calculated, so we don't want sc_super to do its thing
          return false;
        }
        else
          this._justEvaled = NO;
      }
      return true;
    }

  };  // end return from closure
}()) );  // end closure
<|endoftext|>


<commit_before>import fs from 'fs';
import path from 'path';
import appRootDir from 'app-root-dir';

export default () => {
  const rootDir = appRootDir.get();
  const setupTestFilePath = path.resolve(rootDir, 'test', 'setup-test.js');
  const setupTestsFile = fs.existsSync(setupTestFilePath) ? '<rootDir>/src/setupTests.js' : undefined;

  return {
    rootDir,
    setupTestFrameworkScriptFile: setupTestsFile,
    testEnvironment: 'node',
    testURL: 'http://localhost',
    moduleFileExtensions: [
      'js',
      'jsx',
    ],
    moduleDirectories: [
      'src',
      'shared',
      'node_modules',
    ],
    transform: {
      '^.+\\.(js|jsx)$': path.resolve(__dirname, 'babel-transform.js'),
    },
    moduleNameMapper: {
      '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': path.resolve(__dirname, 'asset-mock.js'),
      '\\.(css|less)$': path.resolve(__dirname, 'style-mock.js'),
    },
    testPathIgnorePatterns: [
      '<rootDir>[/\\\\](build|docs|node_modules|scripts)[/\\\\]',
      '/_fixtures',
    ],
    collectCoverage: true,
    coverageReporters: [
      'json',
      'html',
      'text-summary',
    ],
    collectCoverageFrom: ['src/**/*.{js,jsx}'],
  };
};
<commit_msg>check for setup test file<commit_after>import fs from 'fs';
import path from 'path';
import appRootDir from 'app-root-dir';

export default () => {
  const rootDir = appRootDir.get();
  const setupTestFilePath = path.resolve(rootDir, 'stanza', 'setup-test.js');
  const setupTestsFile = fs.existsSync(setupTestFilePath) ? setupTestFilePath : undefined;

  return {
    rootDir,
    setupTestFrameworkScriptFile: setupTestsFile,
    testEnvironment: 'node',
    testURL: 'http://localhost',
    moduleFileExtensions: [
      'js',
      'jsx',
    ],
    moduleDirectories: [
      'src',
      'shared',
      'node_modules',
    ],
    transform: {
      '^.+\\.(js|jsx)$': path.resolve(__dirname, 'babel-transform.js'),
    },
    moduleNameMapper: {
      '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': path.resolve(__dirname, 'asset-mock.js'),
      '\\.(css|less)$': path.resolve(__dirname, 'style-mock.js'),
    },
    testPathIgnorePatterns: [
      '<rootDir>[/\\\\](build|docs|node_modules|scripts)[/\\\\]',
      '/_fixtures',
    ],
    collectCoverage: true,
    coverageReporters: [
      'json',
      'html',
      'text-summary',
    ],
    collectCoverageFrom: ['src/**/*.{js,jsx}'],
  };
};
<|endoftext|>


<commit_before>/*!***************************************************
 * jmHighlight
 * Version 2.3.3
 * Copyright (c) 2014-2015, Julian Motz
 * For the full copyright and license information,
 * please view the LICENSE file that was distributed
 * with this source code.
 *****************************************************/
// TODO: Provide option to remove highlight on the same instance
(function (global, factory) {
	"use strict";
	if(typeof define === "function" && define.amd) {
		// RequireJS. Register as an anonymous module.
		define(["jquery"], function(jQuery) {
			return factory(jQuery, global);
		});
	} else if (typeof exports === "object") {
		 // Node/CommonJS
		 factory(require("jquery"), global);
	} else {
		// Browser globals
		factory(global.jQuery, global);
	}
})(this, function (jQuery) {
	"use strict";

	/**
	 * Map jQuery
	 */
	var $ = jQuery;

	/**
	 * Instance initialization
	 *
	 * @return this
	 */
	function jmHighlight($context_, keyword_, options_){
		// Initialize options
		this.options = $.extend({}, {
			"debug": false,
			"element": "span",
			"className": "highlight",
			"filter": [],
			"separateWordSearch": false,
			"diacritics": true
		}, options_);
		// Initialize keyword
		this.keyword = typeof keyword_ === "string" ? keyword_: "";
		// Initialize elements
		this.$elements = $();
		if($context_ instanceof $ && $context_.length > 0){
			this.$elements = $context_.add($context_.find("*"));
			// Filter elements if filter is defined
			var tmp = this.getFilteredElements();
			if(tmp != false){
				this.$elements = tmp;
			}
		}
		return this;
	}

	/**
	 * Diacritic symbols
	 * for multilanguage purposes
	 */
	jmHighlight.prototype.diacritics = [
		"aÀÁÂÃÄÅàáâãäåĀāąĄ",
		"cÇçćĆčČ",
		"dđĐďĎ",
		"eÈÉÊËèéêëěĚĒēęĘ",
		"iÌÍÎÏìíîïĪī",
		"lłŁ",
		"nÑñňŇńŃ",
		"oÒÓÔÕÕÖØòóôõöøŌō",
		"rřŘ",
		"sŠšśŚ",
		"tťŤ",
		"uÙÚÛÜùúûüůŮŪū",
		"yŸÿýÝ",
		"zŽžżŻźŹ"
	];

	/**
	 * Filters elements based on the
	 * defined filter and saves the result
	 * in the elements variable
	 *
	 * @return jquery-object
	 */
	jmHighlight.prototype.getFilteredElements = function(){
		var filterArr = this.options["filter"];
		if(typeof filterArr !== "object" || this.$elements instanceof $ == false
			|| Object.prototype.toString.call(filterArr) != '[object Array]'
		){
			return false;
		}
		var $contextElements = this.$elements;
		$contextElements = $contextElements.filter(function(){
			var $this = $(this);
			// Check if match in element itself
			var foundInElement = false;
			for(var i = 0, ilength = filterArr.length; i < ilength; i++){
				var filter = filterArr[i];
				// Use is() instead of hasClass() to
				// support complex selectors
				if($this.is(filter)){
					foundInElement = true;
					continue;
				}
			}
			if(foundInElement){
				// Delete entry
				return false;
			} else {
				// Remain entry
				return true;
			}
		});
		return $contextElements;
	}

	/**
	 * Gets non recursive nodes of an element and returns
	 * it as an array
	 *
	 * @param jquery-object $elements_
	 * @return array
	 */
	jmHighlight.prototype.getTextNodes = function($elements_){
		var arr = [];
		if($elements_ instanceof $ == false || $elements_.length == 0){
			return arr;
		}
		// Iterate over all items in the stack
		$elements_.each(function(){
			var $this = $(this);

			// Get all text nodes of this element (not recursive!)
			var $nodes = $this.contents().filter(function(){
				if(this.nodeType == 3){
					return true;
				} else {
					return false;
				}
			});

			// Iterate over that text nodes and call callback
			$nodes.each(function(){
				arr.push(this);
			});
		});
		return arr;
	}

	/**
	 * Creates a regular expression
	 * based on the current keyword
	 * and includes the diacritics
	 * if defined
	 *
	 * @param string keyword_ (optional if you don't want to use this.keyword)
	 * @return string
	 */
	jmHighlight.prototype.getKeywordRegexp = function(keyword_){
		var keyword = typeof keyword_ !== "string" ? this.keyword: keyword_;
		if(typeof keyword !== "string" || keyword == ""){
			return keyword;
		}
		var regexp = keyword;
		// If diacritics is defined we need to phrase
		// the regexp to match also diacritic characters
		if(this.options["diacritics"]){
			var charArr = keyword.split('');
			var handled = [];
			for(var k = 0, klength = charArr.length; k < klength; k++){
				var ch = charArr[k];
				for(var j = 0, jlength = this.diacritics.length; j < jlength; j++){
					var diacritic = this.diacritics[j];
					if(diacritic.indexOf(ch) != -1){
						if(handled.indexOf(diacritic) > -1){// check if already handled
							continue;
						}
						// Match found. Now replace all
						// characters in this diacritic-list
						// with the regex expression
						// (replace all characters in a diacritic-list
						// since all characters in that list will not get
						// handled anymore)
						regexp = regexp.replace(
							new RegExp("[" + diacritic + "]", "gmi"),
							"[" + diacritic + "]"
						);
						handled.push(diacritic);
					}
				}
			}
		}
		return regexp;
	}

	/**
	 * Highlighting
	 *
	 * @param string keyword_ (optional, will be used for recursive calls)
	 * @return bool
	 */
	jmHighlight.prototype.highlight = function(keyword_){
		// If the keyword is a blank it is not a error because
		// the user does not expect that anything will be highlighted.
		// So we will still return true
		var keyword = typeof keyword_ !== "string" ? this.keyword: keyword_;
		if(keyword == ""){
			return true;
		}
		if(this.$elements.length == 0){
			if(this.options["debug"]){
				console.log("No search context provided");
			}
			return false;
		}

		// Filter all elements that were already highlighted
		// (e.g. if separateWordSearch is true)
		this.$elements = this.$elements.filter("*:not([data-jmHighlight])");

		// If there are multiple keywords and separate word search
		// is configured then highlight them
		// all separately
		var sepWS = this.options["separateWordSearch"];
		if(typeof sepWS === "boolean" && sepWS){
			var spl = keyword.split(" ");
			if(spl.length > 1){
				if(this.options["debug"]){
					console.log("Highlighting keywords separately");
				}
				for(var j = 0, jlength = spl.length; j < jlength; j++){
					// Call the highlight function for each
					// separate keyword.

					if(!this.highlight(spl[j])){
						return false;
					}
				}
				return true;
			}
		}

		if(this.options["debug"]){
			console.log("Highlighting keyword '" + keyword + "' in elements:");
			console.log(this.$elements);
		}
		// Get the regular expression including diacritics if defined
		var regexp = this.getKeywordRegexp(keyword);
		// Build the expression to search in node values
		// Note: Don't search with the "g" flag: http://tinyurl.com/q5hdctj
		var nodeMatchRegex = new RegExp(regexp, "mi");
		// Iterate over all text nodes, find matches and replace
		// the search keyword with the highlighting element
		var textNodes = this.getTextNodes(this.$elements);
		for(var i = 0, length = textNodes.length; i < length; i++){
			var node = textNodes[i];
			if(
				typeof node !== "object" ||
				typeof node.nodeValue !== "string" ||
				node.nodeValue.trim() == ""
			){
				continue;
			}
			if(nodeMatchRegex.test(node.nodeValue) === false){
				continue;
			}
			if(this.options["debug"]){
				console.log("Regex: '" + regexp + "'. Node value: '" + node.nodeValue + "'");
			}
			var tagO = "<" + this.options["element"] + " class='" + this.options["className"] +
						"' data-jmHighlight='true'>";
			var tagC = "</" + this.options["element"] + ">";
			if(node.parentNode != null){
				// Don't search inside HTML tags (e.g. keyword "data"
				// would match because of data-xyz inside HTML tag).
				// Replace it with the original match, e.g. if the
				// search keyword is "g" replace it with "g" and not "G"
				var regex = new RegExp("((?![^<]*>)" + regexp + ")", "gim");
				node.parentNode.innerHTML = node.parentNode.innerHTML.replace(
					regex,
					tagO + "$1" + tagC
				);
			}
		}
		return true;
	};

	/**
	 * Highlighting removal
	 *
	 * @return bool
	 */
	jmHighlight.prototype.removeHighlight = function(){
		if(this.$elements.length == 0){
			if(this.options["debug"]){
				console.log("No search context provided");
			}
			return false;
		}
		if(this.options["debug"]){
			if(typeof this.keyword === "string" && this.keyword != ""){
				console.log("Removing highlighting with keyword: '" + this.keyword + "'");
			} else {
				console.log("Removing highlighting");
			}
		}
		var regex = new RegExp(this.getKeywordRegexp(), "mi");
		var find = this.options["element"] + "[data-jmHighlight]." + this.options["className"];
		var parentScope = this;
		var $stack = this.$elements.filter(find);
		if(this.options["debug"]){
			console.log(find);
			console.log($stack);
		}
		$stack.each(function(){
			var $this = $(this);
			if(!regex.test($this.text())){
				return true;
			} else {
				// Remove element with this text
				// @notice: When removing the HTML node
				// with just the text, it will remain a separate
				// text node for the replaced text. Because
				// the highlighting finds only text nodes
				// with the whole keyword inside, we need
				// to append the next text node with the text. That will
				// avoid having separate text nodes.
				parentScope.appendTextNodes($this, $stack);
			}
		});
		return true;
	};

	/**
	 * Will append the domElement with previous
	 * or next elements if they are text nodes
	 * or also highlighting elements. This prevents
	 * that the highlight removal will split text nodes.
	 *
	 * @param jquery-object $domElement
	 * @param jquery-object $stack
	 * @return bool
	 */
	jmHighlight.prototype.appendTextNodes = function($domElement, $stack){
		if($domElement instanceof $ == false || $domElement.length == 0){
			return false;
		}

		var domElement = $domElement.first()[0];
		var prevSibling = domElement.previousSibling;
		var nextSibling = domElement.nextSibling;
		var $prevSibling = $(prevSibling);
		var $nextSibling = $(nextSibling);

		var handled = false;
		// Check if there is a previous text node or
		// highlight element. If so, append those two elements
		// with the text content
		if($prevSibling.length > 0){
			if(prevSibling.nodeType == 3){
				// It's a text node
				prevSibling.nodeValue = prevSibling.nodeValue + $domElement.text();
				$domElement.remove();
			} else if($stack instanceof $ && $stack.length > 0 && $stack.is($prevSibling)){
				// It's another highlighting element
				$prevSibling.replaceWith($domElement.text() + $prevSibling.text());
				$domElement.remove();
			} else {
				$domElement.replaceWith($domElement.text());
			}
			handled = true;
		}

		// Check if there is a next text node or
		// highlight element. If so, append those two elements
		// with the text node
		if($nextSibling.length > 0){
			if(nextSibling.nodeType == 3){
				if($prevSibling.length == 0){
					nextSibling.nodeValue = $domElement.text() + nextSibling.nodeValue;
					$domElement.remove();
				} else {
					nextSibling.nodeValue = $prevSibling.text() + nextSibling.nodeValue;
					$prevSibling.remove();
				}
			} else if($stack instanceof $ && $stack.length > 0 && $stack.is($nextSibling)){
				if($prevSibling.length == 0){
					$nextSibling.replaceWith($nextSibling.text() + $domElement.text());
					$domElement.remove();
				} else {
					$nextSibling.replaceWith($prevSibling.text() + $nextSibling.text());
					$prevSibling.remove();
				}
			} else {
				if($prevSibling.length == 0){
					$domElement.replaceWith($domElement.text());
				}
			}
			handled = true;
		}

		// If there is no next or prev element
		// simply remove the element with it's content
		if(handled == false){
			$domElement.replaceWith($domElement.text());
		}

		return true;
	};

	/**
	 * jmHighlight component exposure for jQuery
	 *
	 * @return boolean
	 */
	$.fn.jmHighlight = function(keyword_, options_){
		var highlightInstance = new jmHighlight($(this), keyword_, options_);
		return highlightInstance.highlight();
	};
	$.fn.jmRemoveHighlight = function(options_, keyword_){
		var highlightInstance = new jmHighlight($(this), keyword_, options_);
		return highlightInstance.removeHighlight();
	};

});

<commit_msg>Updated TODO<commit_after>/*!***************************************************
 * jmHighlight
 * Version 2.3.3
 * Copyright (c) 2014-2015, Julian Motz
 * For the full copyright and license information,
 * please view the LICENSE file that was distributed
 * with this source code.
 *****************************************************/
// TODO: Provide option to remove highlight on the same instance
//       Add synonym option
(function (global, factory) {
	"use strict";
	if(typeof define === "function" && define.amd) {
		// RequireJS. Register as an anonymous module.
		define(["jquery"], function(jQuery) {
			return factory(jQuery, global);
		});
	} else if (typeof exports === "object") {
		 // Node/CommonJS
		 factory(require("jquery"), global);
	} else {
		// Browser globals
		factory(global.jQuery, global);
	}
})(this, function (jQuery) {
	"use strict";

	/**
	 * Map jQuery
	 */
	var $ = jQuery;

	/**
	 * Instance initialization
	 *
	 * @return this
	 */
	function jmHighlight($context_, keyword_, options_){
		// Initialize options
		this.options = $.extend({}, {
			"debug": false,
			"element": "span",
			"className": "highlight",
			"filter": [],
			"separateWordSearch": false,
			"diacritics": true
		}, options_);
		// Initialize keyword
		this.keyword = typeof keyword_ === "string" ? keyword_: "";
		// Initialize elements
		this.$elements = $();
		if($context_ instanceof $ && $context_.length > 0){
			this.$elements = $context_.add($context_.find("*"));
			// Filter elements if filter is defined
			var tmp = this.getFilteredElements();
			if(tmp != false){
				this.$elements = tmp;
			}
		}
		return this;
	}

	/**
	 * Diacritic symbols
	 * for multilanguage purposes
	 */
	jmHighlight.prototype.diacritics = [
		"aÀÁÂÃÄÅàáâãäåĀāąĄ",
		"cÇçćĆčČ",
		"dđĐďĎ",
		"eÈÉÊËèéêëěĚĒēęĘ",
		"iÌÍÎÏìíîïĪī",
		"lłŁ",
		"nÑñňŇńŃ",
		"oÒÓÔÕÕÖØòóôõöøŌō",
		"rřŘ",
		"sŠšśŚ",
		"tťŤ",
		"uÙÚÛÜùúûüůŮŪū",
		"yŸÿýÝ",
		"zŽžżŻźŹ"
	];

	/**
	 * Filters elements based on the
	 * defined filter and saves the result
	 * in the elements variable
	 *
	 * @return jquery-object
	 */
	jmHighlight.prototype.getFilteredElements = function(){
		var filterArr = this.options["filter"];
		if(typeof filterArr !== "object" || this.$elements instanceof $ == false
			|| Object.prototype.toString.call(filterArr) != '[object Array]'
		){
			return false;
		}
		var $contextElements = this.$elements;
		$contextElements = $contextElements.filter(function(){
			var $this = $(this);
			// Check if match in element itself
			var foundInElement = false;
			for(var i = 0, ilength = filterArr.length; i < ilength; i++){
				var filter = filterArr[i];
				// Use is() instead of hasClass() to
				// support complex selectors
				if($this.is(filter)){
					foundInElement = true;
					continue;
				}
			}
			if(foundInElement){
				// Delete entry
				return false;
			} else {
				// Remain entry
				return true;
			}
		});
		return $contextElements;
	}

	/**
	 * Gets non recursive nodes of an element and returns
	 * it as an array
	 *
	 * @param jquery-object $elements_
	 * @return array
	 */
	jmHighlight.prototype.getTextNodes = function($elements_){
		var arr = [];
		if($elements_ instanceof $ == false || $elements_.length == 0){
			return arr;
		}
		// Iterate over all items in the stack
		$elements_.each(function(){
			var $this = $(this);

			// Get all text nodes of this element (not recursive!)
			var $nodes = $this.contents().filter(function(){
				if(this.nodeType == 3){
					return true;
				} else {
					return false;
				}
			});

			// Iterate over that text nodes and call callback
			$nodes.each(function(){
				arr.push(this);
			});
		});
		return arr;
	}

	/**
	 * Creates a regular expression
	 * based on the current keyword
	 * and includes the diacritics
	 * if defined
	 *
	 * @param string keyword_ (optional if you don't want to use this.keyword)
	 * @return string
	 */
	jmHighlight.prototype.getKeywordRegexp = function(keyword_){
		var keyword = typeof keyword_ !== "string" ? this.keyword: keyword_;
		if(typeof keyword !== "string" || keyword == ""){
			return keyword;
		}
		var regexp = keyword;
		// If diacritics is defined we need to phrase
		// the regexp to match also diacritic characters
		if(this.options["diacritics"]){
			var charArr = keyword.split('');
			var handled = [];
			for(var k = 0, klength = charArr.length; k < klength; k++){
				var ch = charArr[k];
				for(var j = 0, jlength = this.diacritics.length; j < jlength; j++){
					var diacritic = this.diacritics[j];
					if(diacritic.indexOf(ch) != -1){
						if(handled.indexOf(diacritic) > -1){// check if already handled
							continue;
						}
						// Match found. Now replace all
						// characters in this diacritic-list
						// with the regex expression
						// (replace all characters in a diacritic-list
						// since all characters in that list will not get
						// handled anymore)
						regexp = regexp.replace(
							new RegExp("[" + diacritic + "]", "gmi"),
							"[" + diacritic + "]"
						);
						handled.push(diacritic);
					}
				}
			}
		}
		return regexp;
	}

	/**
	 * Highlighting
	 *
	 * @param string keyword_ (optional, will be used for recursive calls)
	 * @return bool
	 */
	jmHighlight.prototype.highlight = function(keyword_){
		// If the keyword is a blank it is not a error because
		// the user does not expect that anything will be highlighted.
		// So we will still return true
		var keyword = typeof keyword_ !== "string" ? this.keyword: keyword_;
		if(keyword == ""){
			return true;
		}
		if(this.$elements.length == 0){
			if(this.options["debug"]){
				console.log("No search context provided");
			}
			return false;
		}

		// Filter all elements that were already highlighted
		// (e.g. if separateWordSearch is true)
		this.$elements = this.$elements.filter("*:not([data-jmHighlight])");

		// If there are multiple keywords and separate word search
		// is configured then highlight them
		// all separately
		var sepWS = this.options["separateWordSearch"];
		if(typeof sepWS === "boolean" && sepWS){
			var spl = keyword.split(" ");
			if(spl.length > 1){
				if(this.options["debug"]){
					console.log("Highlighting keywords separately");
				}
				for(var j = 0, jlength = spl.length; j < jlength; j++){
					// Call the highlight function for each
					// separate keyword.

					if(!this.highlight(spl[j])){
						return false;
					}
				}
				return true;
			}
		}

		if(this.options["debug"]){
			console.log("Highlighting keyword '" + keyword + "' in elements:");
			console.log(this.$elements);
		}
		// Get the regular expression including diacritics if defined
		var regexp = this.getKeywordRegexp(keyword);
		// Build the expression to search in node values
		// Note: Don't search with the "g" flag: http://tinyurl.com/q5hdctj
		var nodeMatchRegex = new RegExp(regexp, "mi");
		// Iterate over all text nodes, find matches and replace
		// the search keyword with the highlighting element
		var textNodes = this.getTextNodes(this.$elements);
		for(var i = 0, length = textNodes.length; i < length; i++){
			var node = textNodes[i];
			if(
				typeof node !== "object" ||
				typeof node.nodeValue !== "string" ||
				node.nodeValue.trim() == ""
			){
				continue;
			}
			if(nodeMatchRegex.test(node.nodeValue) === false){
				continue;
			}
			if(this.options["debug"]){
				console.log("Regex: '" + regexp + "'. Node value: '" + node.nodeValue + "'");
			}
			var tagO = "<" + this.options["element"] + " class='" + this.options["className"] +
						"' data-jmHighlight='true'>";
			var tagC = "</" + this.options["element"] + ">";
			if(node.parentNode != null){
				// Don't search inside HTML tags (e.g. keyword "data"
				// would match because of data-xyz inside HTML tag).
				// Replace it with the original match, e.g. if the
				// search keyword is "g" replace it with "g" and not "G"
				var regex = new RegExp("((?![^<]*>)" + regexp + ")", "gim");
				node.parentNode.innerHTML = node.parentNode.innerHTML.replace(
					regex,
					tagO + "$1" + tagC
				);
			}
		}
		return true;
	};

	/**
	 * Highlighting removal
	 *
	 * @return bool
	 */
	jmHighlight.prototype.removeHighlight = function(){
		if(this.$elements.length == 0){
			if(this.options["debug"]){
				console.log("No search context provided");
			}
			return false;
		}
		if(this.options["debug"]){
			if(typeof this.keyword === "string" && this.keyword != ""){
				console.log("Removing highlighting with keyword: '" + this.keyword + "'");
			} else {
				console.log("Removing highlighting");
			}
		}
		var regex = new RegExp(this.getKeywordRegexp(), "mi");
		var find = this.options["element"] + "[data-jmHighlight]." + this.options["className"];
		var parentScope = this;
		var $stack = this.$elements.filter(find);
		if(this.options["debug"]){
			console.log(find);
			console.log($stack);
		}
		$stack.each(function(){
			var $this = $(this);
			if(!regex.test($this.text())){
				return true;
			} else {
				// Remove element with this text
				// @notice: When removing the HTML node
				// with just the text, it will remain a separate
				// text node for the replaced text. Because
				// the highlighting finds only text nodes
				// with the whole keyword inside, we need
				// to append the next text node with the text. That will
				// avoid having separate text nodes.
				parentScope.appendTextNodes($this, $stack);
			}
		});
		return true;
	};

	/**
	 * Will append the domElement with previous
	 * or next elements if they are text nodes
	 * or also highlighting elements. This prevents
	 * that the highlight removal will split text nodes.
	 *
	 * @param jquery-object $domElement
	 * @param jquery-object $stack
	 * @return bool
	 */
	jmHighlight.prototype.appendTextNodes = function($domElement, $stack){
		if($domElement instanceof $ == false || $domElement.length == 0){
			return false;
		}

		var domElement = $domElement.first()[0];
		var prevSibling = domElement.previousSibling;
		var nextSibling = domElement.nextSibling;
		var $prevSibling = $(prevSibling);
		var $nextSibling = $(nextSibling);

		var handled = false;
		// Check if there is a previous text node or
		// highlight element. If so, append those two elements
		// with the text content
		if($prevSibling.length > 0){
			if(prevSibling.nodeType == 3){
				// It's a text node
				prevSibling.nodeValue = prevSibling.nodeValue + $domElement.text();
				$domElement.remove();
			} else if($stack instanceof $ && $stack.length > 0 && $stack.is($prevSibling)){
				// It's another highlighting element
				$prevSibling.replaceWith($domElement.text() + $prevSibling.text());
				$domElement.remove();
			} else {
				$domElement.replaceWith($domElement.text());
			}
			handled = true;
		}

		// Check if there is a next text node or
		// highlight element. If so, append those two elements
		// with the text node
		if($nextSibling.length > 0){
			if(nextSibling.nodeType == 3){
				if($prevSibling.length == 0){
					nextSibling.nodeValue = $domElement.text() + nextSibling.nodeValue;
					$domElement.remove();
				} else {
					nextSibling.nodeValue = $prevSibling.text() + nextSibling.nodeValue;
					$prevSibling.remove();
				}
			} else if($stack instanceof $ && $stack.length > 0 && $stack.is($nextSibling)){
				if($prevSibling.length == 0){
					$nextSibling.replaceWith($nextSibling.text() + $domElement.text());
					$domElement.remove();
				} else {
					$nextSibling.replaceWith($prevSibling.text() + $nextSibling.text());
					$prevSibling.remove();
				}
			} else {
				if($prevSibling.length == 0){
					$domElement.replaceWith($domElement.text());
				}
			}
			handled = true;
		}

		// If there is no next or prev element
		// simply remove the element with it's content
		if(handled == false){
			$domElement.replaceWith($domElement.text());
		}

		return true;
	};

	/**
	 * jmHighlight component exposure for jQuery
	 *
	 * @return boolean
	 */
	$.fn.jmHighlight = function(keyword_, options_){
		var highlightInstance = new jmHighlight($(this), keyword_, options_);
		return highlightInstance.highlight();
	};
	$.fn.jmRemoveHighlight = function(options_, keyword_){
		var highlightInstance = new jmHighlight($(this), keyword_, options_);
		return highlightInstance.removeHighlight();
	};

});

<|endoftext|>


<commit_before>/**
* @name             Simple Timer
* @descripton       A jQuery plugin that adds a timer to an element with an
*                   assigned interval and duration.
*
* @version          0.1.5
* @requires         Jquery 1.4+
*
* @author           Ben Gullotti
* @author-email     ben@bengullotti.com
* @author-site      https://github.com/YodaPop
*
* @license          MIT License -
*                   http://www.opensource.org/licenses/mit-license.php
**/

(function($) {

	/**
	 * An object containing the public properties used for the timer plugin set
	 * to their default settings.
	 *
	 * @property _settings
	 * @type Object
	 * @private
	 **/
	var _settings = {
		increment       :   1000,
		duration        :   1000,
		autostart       :   false,
		// callbacks
		onComplete      :   false,
		onIncrement     :   false,
		onStart         :   false,
		onStop          :   false,
		onReset         :   false,
	},

	/**
	 * Starts a timeout that lasts for the plugin's increment value.
	 *
	 * @method _time
	 * @param {Object} The settings the plugin was initialized with
	 * @private
	 **/
	_time = function ( settings ) {
		// set timeout
		if ( settings.timeout !== false ) {
			var el = this;
			settings.timeout = setTimeout( function() {
				_count.call(el, settings);
			}, settings.increment);
		}
	},

	/**
	 * Counts the increments until the plugin's duration is reached.
	 *
	 * @method _count
	 * @param {Object} The settings the plugin was initialized with
	 * @private
	 **/
	_count = function ( settings ) {
		// increase count
		settings.count += settings.increment;
		// on increment event
		if ( settings.onIncrement ) {
			settings.onIncrement.call(this, settings);
		}
		// check complete
		if ( settings.count >= settings.duration ) {
			// on complete event
			if( settings.onComplete ) {
				settings.onComplete.call(this, settings);
			}
			// stop timer
			methods.stop.call($(this));

			return true;
		}
		// timeout
		_time.call(this, settings);
	},

	/**
	 * Getter functions called using
	 * $(selector).simpleTimer('get' + methodName) or
	 * $.simpleTimer('get' + methodName)
	 * WARNING: These methods are not chainable.
	 */
	get = {

		/**
		 * Get the private default settings object used to initialize the
		 * public settings of the plugin.
		 *
		 * @method get.defaultSettings
		 * @return {Object} The default settings object
		 **/
		defaultSettings : function() {
			// apply to each element
			return _settings;
		},

		/**
		 * Get the percentage  of how close the timer is to its duration. The
		 * percentage is calculated based on the increment and the duration.
		 * Thus, a smaller increment relative to the duration will yield a more
		 * accurate percentage.
		 *
		 * @method get.percent
		 * @return {Mixed} Returns a single floating point value between 0 and 1
		 * if one element was selected, otherwise it returns an array of
		 * floating points. If an element has not been initialized, the value
		 * defaults to false.
		 **/
		percent : function() {
			// no elements were selected
			if ( this.length === 0 ) {
				return false;
			}
			// the array of percentages
			var arr = [];
			// loop through the elements
			$(this).each(function() {
				var settings = $(this).data('SimpleTimer.settings');
				if ( settings === undefined ) {
					arr.push(false);
				}else {
					// set percent to the nearest 1000th
					arr.push(Math.round(settings.count /
						settings.duration * 1000) / 1000);
				}
			});

			if ( this.length === 1 ) {
				// return for one element
				return arr[0];
			}else {
				// return for multiple selected elements
				return arr;
			}
		},

	},

	/**
	 * Filters applied before method calls.
	 */
	filters = {

		/**
		 * A filter applied before all methods are called. The filter ensures
		 * that all of the jQuery objects selected were initialized.
		 *
		 * @method filters.methods
		 * @param {Object} method The method about to be filtered
		 * @return {Object} The jQuery object from which the method was called
		 * @chainable
		 **/
		methods : function( method ) {
			// filter out the uninitialized
			var filtered = this.filter(function() {
				if ( $(this).data('SimpleTimer.settings') === undefined ) {
					$.error('Simple Timer Error: method "' + method + '" was ' +
						'called on an element which has not been initialized.');

					return false;
				}

				return true;
			});

			// check filtered before proceeding
			if ( filtered.length > 0 ) {
				// call method
				if ( filters[method] ) {
					// filtered method
					filters[method].apply( filtered, arguments );
				}else {
					// unfiltered method
					methods[method].apply( filtered, arguments );
				}
			}

			// return the jQuery object to keep the method chainable
			return this;
		},

		/**
		 * A filter applied before the start method is called. The filter
		 * checks to see if any of the elements' timers are already timing.
		 *
		 * @method filters.start
		 * @return {Boolean} Returns true if some of the selected jQuery objects
		 * passed the filter, false if none of the objects passed
		 * @chainable
		 **/
		start : function() {
			var filtered = this.filter(function() {
				var settings = $(this).data('SimpleTimer.settings');
				// checks if the timer has already been started
				if ( settings.timeout !== false ) {
					return false;
				}
				// checks if the timer has already run its duration
				if ( settings.count >= settings.duration ) {
					return false;
				}

				return true;
			});

			// check filtered before proceeding
			if ( filtered.length === 0 ) {
				return false;
			}

			// call start
			methods.start.apply(filtered, arguments);

			return true;
		},

		/**
		 * A filter applied before the stop method is called. The filter
		 * checks to see if any of the elements' timers are already timing.
		 *
		 * @method filters.stop
		 * @return {Boolean} Returns true if some of the selected jQuery objects
		 * passed the filter, false if none of the objects passed
		 * @chainable
		 **/
		stop : function() {
			var filtered = this.filter(function() {
				var settings = $(this).data('SimpleTimer.settings');
				// checks if the timer has already been stopped
				if ( settings.timeout === false ) {
					return false;
				}

				return true;
			});

			// check filtered before proceeding
			if ( filtered.length === 0 ) {
				return false;
			}

			// call stop
			methods.stop.apply(filtered, arguments);

			return true;
		},

	},

	/**
	 * Publicly accessible methods called via
	 * $(selector).simpleSlides(methodName).
	 */
	methods = {

		/**
		 * The initialization method. Used to set the properties of the timer
		 * and attach the data to the selected jQuery objects.
		 *
		 * @method methods.init
		 * @param {Object} options An object used to set publicly accessible
		 * options such as the timer's increment, duration, and callbacks (see
		 * README.md for details)
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		init : function( options ) {

			/*
			* Create some defaults. Extend them with any options that were
			* provided.
			*/
			var settings = $.extend( true, {}, _settings, options,
			// private settings
			{
				timeout         :   false,
				count           :   0,
			});

			return this.each(function(){
				// save data
				$(this).data('SimpleTimer.settings', settings);
				// autostart the timer
				if ( settings.autostart ) {
					methods.start.call($(this));
				}
			});
		},

		/**
		 * Starts the timer.
		 *
		 * @method methods.start
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		start : function() {
			// apply to each element
			return this.each( function() {
				// retrieve settings
				var settings = $(this).data('SimpleTimer.settings');
				// start timing boolean
				settings.timeout = true;
				// on start event
				if ( settings.onStart ) {
					settings.onStart.call(this, settings);
				}
				// start timing
				_time.call(this, settings);
			});
		},

		/**
		 * Stops the timer.
		 *
		 * @method methods.stop
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		stop : function() {
			// apply to each element
			return this.each( function() {
				// retrieve settings
				var settings = $(this).data('SimpleTimer.settings');
				// clear timeout
				clearTimeout(settings.timeout);
				// stop timing boolean
				settings.timeout = false;
				// on stop event
				if ( settings.onStop ) {
					settings.onStop.call(this, settings);
				}
			});
		},

		/**
		 * Resets the timer. The timer's count is reset to 0.
		 *
		 * @method methods.reset
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		reset : function() {
			// apply to each element
			return this.each( function() {
				// retrieve settings
				var settings = $(this).data('SimpleTimer.settings');
				// stop the timer
				methods.stop.call($(this));
				// reset the count
				settings.count = 0;
				// on reset event
				if ( settings.onReset ) {
					settings.onReset.call(this, settings);
				}
			});
		},

		/**
		 * Updates the options for the plugin and saves the data.
		 *
		 * @method methods.update
		 * @param {Object} options The options that will be used to override the
		 * current options
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		update : function( options ) {
			// apply to each element
			return this.each( function() {
				// retrieve settings
				var settings = $(this).data('SimpleTimer.settings');
				// update the settings with the new options
				settings = $.extend(true, settings, options);
				// save data
				$(this).data('SimpleTimer.settings', settings);
			});
		},

		/**
		 * Destroys the timer by deleting the settings attached to the DOM
		 * element.
		 *
		 * @method methods.destroy
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		destroy : function() {
			// apply to each element
			return this.each( function() {
				// reset plugin
				methods.reset.call($(this));
				// remove previously stored data
				$(this).removeData('SimpleTimer.settings');
			});
		},

	};

	// jQuery selected

	$.fn.simpleTimer = function( method ) {
		if ( typeof method === 'string' ) {
			if ( method.substr(0, 3) === 'get' ) {
				method = method.substr(3, 1).toLowerCase() +
					method.substr(4);
				if ( get[method] ) {
					// getter functions (not chainable)
					return get[method].call(this);
				}else {
					$.error('Simple Timer Error: getter function ' +  method +
						' does not exist.');
				}
			} else if ( methods[method] ) {
				// filtered methods
				return filters.methods.apply( this, arguments );
			} else {
				$.error('Simple Timer Error: method ' +  method +
					' does not exist.');
			}
		} else if ( typeof method === 'object' || ! method ) {
			// initialize the plugin
			return methods.init.apply( this, arguments );
		}
		// general exception
		$.error('Simple Timer Error: the simple timer plugin expects at' +
			'least 1 paramater passed for inititialization. The first ' +
			'paramater must be of type "string" or "object"');
	};

	// jQuery object (get functions only)

	$.simpleTimer = function( method ) {
		if ( typeof method === 'string' &&
			 method.substr(0, 3) === 'get') {
			method = method.substr(3, 1).toLowerCase() +
				method.substr(4);
			if ( get[method] ) {
				// getter functions (not chainable)
				return get[method].call([]);
			}else {
				$.error('Simple Timer Error: getter function ' +  method +
					' does not exist.');
			}
		}
		// general exception
		$.error('Simple Timer Error: direct calls to simpleTimer only work ' +
			'with get functions');
	};

})(jQuery);
<commit_msg>Added getSetting and getSettings functions. Fixed arguments array bug.<commit_after>/**
* @name             Simple Timer
* @descripton       A jQuery plugin that adds a timer to an element with an
*                   assigned interval and duration.
*
* @version          0.1.5
* @requires         Jquery 1.4+
*
* @author           Ben Gullotti
* @author-email     ben@bengullotti.com
* @author-site      https://github.com/YodaPop
*
* @license          MIT License -
*                   http://www.opensource.org/licenses/mit-license.php
**/

(function($) {

	/**
	 * An object containing the public properties used for the timer plugin set
	 * to their default settings.
	 *
	 * @property _settings
	 * @type Object
	 * @private
	 **/
	var _settings = {
		increment       :   1000,
		duration        :   1000,
		autostart       :   false,
		// callbacks
		onComplete      :   false,
		onIncrement     :   false,
		onStart         :   false,
		onStop          :   false,
		onReset         :   false,
	},

	/**
	 * Starts a timeout that lasts for the plugin's increment value.
	 *
	 * @method _time
	 * @param {Object} The settings the plugin was initialized with
	 * @private
	 **/
	_time = function ( settings ) {
		// set timeout
		if ( settings.timeout !== false ) {
			var el = this;
			settings.timeout = setTimeout( function() {
				_count.call(el, settings);
			}, settings.increment);
		}
	},

	/**
	 * Counts the increments until the plugin's duration is reached.
	 *
	 * @method _count
	 * @param {Object} The settings the plugin was initialized with
	 * @private
	 **/
	_count = function ( settings ) {
		// increase count
		settings.count += settings.increment;
		// on increment event
		if ( settings.onIncrement ) {
			settings.onIncrement.call(this, settings);
		}
		// check complete
		if ( settings.count >= settings.duration ) {
			// on complete event
			if( settings.onComplete ) {
				settings.onComplete.call(this, settings);
			}
			// stop timer
			methods.stop.call($(this));

			return true;
		}
		// timeout
		_time.call(this, settings);
	},

	/**
	 * Getter functions called using
	 * $(selector).simpleTimer('get' + methodName) or
	 * $.simpleTimer('get' + methodName)
	 * WARNING: These methods are not chainable.
	 */
	get = {

		/**
		 * Get the private default settings object used to initialize the
		 * public settings of the plugin.
		 *
		 * @method get.defaultSettings
		 * @return {Object} The default settings object
		 **/
		defaultSettings : function() {
			// apply to each element
			return _settings;
		},

		/**
		 * Get a specific setting passed in as a string.
		 *
		 * @method get.settings
		 * @param {String} The name of a specific setting
		 * @return {Mixed} Returns the value of the settings or, if there is
		 * more than one element selected, an array of values. If no settings
		 * exist on an element, it defaults to false.
		 **/
		settings : function( str ) {
			// no elements were selected
			if ( this.length === 0 ) {
				return false;
			}
			// the return array
			var arr = [];
			// loop through the elements
			$(this).each(function() {
				var settings = $(this).data('SimpleTimer.settings');
				if ( settings === undefined ) {
					// default to false
					arr.push(false);
				}else {
					// add settings
					arr.push(settings);
				}
			});

			if ( this.length === 1 ) {
				// return for one element
				return arr[0];
			}else {
				// return for multiple selected elements
				return arr;
			}
		},

		/**
		 * Get a specific setting passed in as a string.
		 *
		 * @method get.setting
		 * @param {String} The name of a specific setting
		 * @return {Mixed} Returns the value of the setting or, if there is more
		 * than one element selected, an array of values. If no settings exist
		 * on an element, it defaults to false.
		 **/
		setting : function( str ) {
			// no elements were selected
			if ( this.length === 0 ) {
				return false;
			}
			// the return array
			var arr = [];
			// loop through the elements
			$(this).each(function() {
				var settings = $(this).data('SimpleTimer.settings');
				if ( settings === undefined ||
					 settings[str] === undefined ) {
					// default to false
					arr.push(false);
				}else {
					// add setting
					arr.push(settings[str]);
				}
			});

			if ( this.length === 1 ) {
				// return for one element
				return arr[0];
			}else {
				// return for multiple selected elements
				return arr;
			}
		},

		/**
		 * Get the percentage  of how close the timer is to its duration on the
		 * selected elements. The percentage is calculated based on the
		 * increment and the duration. Thus, a smaller increment relative to
		 * the duration will yield a more accurate percentage.
		 *
		 * @method get.percent
		 * @return {Mixed} Returns a single floating point value between 0 and 1
		 * if one element was selected, otherwise it returns an array of
		 * floating points. If an element has not been initialized, the value
		 * defaults to false.
		 **/
		percent : function() {
			// no elements were selected
			if ( this.length === 0 ) {
				return false;
			}
			// the return array
			var arr = [];
			// loop through the elements
			$(this).each(function() {
				var settings = $(this).data('SimpleTimer.settings');
				if ( settings === undefined ) {
					// default to false
					arr.push(false);
				}else {
					// set percent to the nearest 1000th
					arr.push(Math.round(settings.count /
						settings.duration * 1000) / 1000);
				}
			});

			if ( this.length === 1 ) {
				// return for one element
				return arr[0];
			}else {
				// return for multiple selected elements
				return arr;
			}
		},

	},

	/**
	 * Filters applied before method calls.
	 */
	filters = {

		/**
		 * A filter applied before all methods are called. The filter ensures
		 * that all of the jQuery objects selected were initialized.
		 *
		 * @method filters.methods
		 * @param {Object} method The method about to be filtered
		 * @return {Object} The jQuery object from which the method was called
		 * @chainable
		 **/
		methods : function( method ) {
			// filter out the uninitialized
			var filtered = this.filter(function() {
				if ( $(this).data('SimpleTimer.settings') === undefined ) {
					$.error('Simple Timer Error: method "' + method + '" was ' +
						'called on an element which has not been initialized.');

					return false;
				}

				return true;
			});

			// check filtered before proceeding
			if ( filtered.length > 0 ) {
				// call method
				if ( filters[method] ) {
					// filtered method
					filters[method].apply( filtered,
						Array.prototype.slice.call(arguments, 1) );
				}else {
					// unfiltered method
					methods[method].apply( filtered,
						Array.prototype.slice.call(arguments, 1) );
				}
			}

			// return the jQuery object to keep the method chainable
			return this;
		},

		/**
		 * A filter applied before the start method is called. The filter
		 * checks to see if any of the elements' timers are already timing.
		 *
		 * @method filters.start
		 * @return {Boolean} Returns true if some of the selected jQuery objects
		 * passed the filter, false if none of the objects passed
		 * @chainable
		 **/
		start : function() {
			var filtered = this.filter(function() {
				var settings = $(this).data('SimpleTimer.settings');
				// checks if the timer has already been started
				if ( settings.timeout !== false ) {
					return false;
				}
				// checks if the timer has already run its duration
				if ( settings.count >= settings.duration ) {
					return false;
				}

				return true;
			});

			// check filtered before proceeding
			if ( filtered.length === 0 ) {
				return false;
			}

			// call start
			methods.start.apply(filtered, arguments);

			return true;
		},

		/**
		 * A filter applied before the stop method is called. The filter
		 * checks to see if any of the elements' timers are already timing.
		 *
		 * @method filters.stop
		 * @return {Boolean} Returns true if some of the selected jQuery objects
		 * passed the filter, false if none of the objects passed
		 * @chainable
		 **/
		stop : function() {
			var filtered = this.filter(function() {
				var settings = $(this).data('SimpleTimer.settings');
				// checks if the timer has already been stopped
				if ( settings.timeout === false ) {
					return false;
				}

				return true;
			});

			// check filtered before proceeding
			if ( filtered.length === 0 ) {
				return false;
			}

			// call stop
			methods.stop.apply(filtered, arguments);

			return true;
		},

	},

	/**
	 * Publicly accessible methods called via
	 * $(selector).simpleSlides(methodName).
	 */
	methods = {

		/**
		 * The initialization method. Used to set the properties of the timer
		 * and attach the data to the selected jQuery objects.
		 *
		 * @method methods.init
		 * @param {Object} options An object used to set publicly accessible
		 * options such as the timer's increment, duration, and callbacks (see
		 * README.md for details)
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		init : function( options ) {

			/*
			* Create some defaults. Extend them with any options that were
			* provided.
			*/
			var settings = $.extend( true, {}, _settings, options,
			// private settings
			{
				timeout         :   false,
				count           :   0,
			});

			return this.each(function(){
				// save data
				$(this).data('SimpleTimer.settings', settings);
				// autostart the timer
				if ( settings.autostart ) {
					methods.start.call($(this));
				}
			});
		},

		/**
		 * Starts the timer.
		 *
		 * @method methods.start
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		start : function() {
			// apply to each element
			return this.each( function() {
				// retrieve settings
				var settings = $(this).data('SimpleTimer.settings');
				// start timing boolean
				settings.timeout = true;
				// on start event
				if ( settings.onStart ) {
					settings.onStart.call(this, settings);
				}
				// start timing
				_time.call(this, settings);
			});
		},

		/**
		 * Stops the timer.
		 *
		 * @method methods.stop
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		stop : function() {
			// apply to each element
			return this.each( function() {
				// retrieve settings
				var settings = $(this).data('SimpleTimer.settings');
				// clear timeout
				clearTimeout(settings.timeout);
				// stop timing boolean
				settings.timeout = false;
				// on stop event
				if ( settings.onStop ) {
					settings.onStop.call(this, settings);
				}
			});
		},

		/**
		 * Resets the timer. The timer's count is reset to 0.
		 *
		 * @method methods.reset
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		reset : function() {
			// apply to each element
			return this.each( function() {
				// retrieve settings
				var settings = $(this).data('SimpleTimer.settings');
				// stop the timer
				methods.stop.call($(this));
				// reset the count
				settings.count = 0;
				// on reset event
				if ( settings.onReset ) {
					settings.onReset.call(this, settings);
				}
			});
		},

		/**
		 * Updates the options for the plugin and saves the data.
		 *
		 * @method methods.update
		 * @param {Object} options The options that will be used to override the
		 * current options
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		update : function( options ) {
			// apply to each element
			return this.each( function() {
				// retrieve settings
				var settings = $(this).data('SimpleTimer.settings');
				// update the settings with the new options
				settings = $.extend(true, settings, options);
				// save data
				$(this).data('SimpleTimer.settings', settings);
			});
		},

		/**
		 * Destroys the timer by deleting the settings attached to the DOM
		 * element.
		 *
		 * @method methods.destroy
		 * @return {Object} The jQuery object's from which the method was called
		 * @chainable
		 **/
		destroy : function() {
			// apply to each element
			return this.each( function() {
				// reset plugin
				methods.reset.call($(this));
				// remove previously stored data
				$(this).removeData('SimpleTimer.settings');
			});
		},

	};

	// jQuery selected

	$.fn.simpleTimer = function( method ) {
		if ( typeof method === 'string' ) {
			if ( method.substr(0, 3) === 'get' ) {
				method = method.substr(3, 1).toLowerCase() +
					method.substr(4);
				if ( get[method] ) {
					// getter functions (not chainable)
					return get[method].apply( this,
						Array.prototype.slice.call(arguments, 1) );
				}else {
					$.error('Simple Timer Error: getter function ' +  method +
						' does not exist.');
				}
			} else if ( methods[method] ) {
				// filtered methods
				return filters.methods.apply( this, arguments );
			} else {
				$.error('Simple Timer Error: method ' +  method +
					' does not exist.');
			}
		} else if ( typeof method === 'object' || ! method ) {
			// initialize the plugin
			return methods.init.apply( this, arguments );
		}
		// general exception
		$.error('Simple Timer Error: the simple timer plugin expects at' +
			'least 1 paramater. The first paramater must be of type "string" ' +
			'or "object"');
	};

	// jQuery object (get functions only)

	$.simpleTimer = function( method ) {
		if ( typeof method === 'string' &&
			 method.substr(0, 3) === 'get') {
			method = method.substr(3, 1).toLowerCase() +
				method.substr(4);
			if ( get[method] ) {
				// getter functions (not chainable)
					return get[method].apply( [],
						Array.prototype.slice.call(arguments, 1) );
			}else {
				$.error('Simple Timer Error: getter function "' +  method +
					'" does not exist.');
			}
		}
		// general exception
		$.error('Simple Timer Error: direct calls to simpleTimer only works ' +
			'with get functions');
	};

})(jQuery);
<|endoftext|>