User:Tommy Ekola/LogCalculator

From MediaWiki.org
Jump to: navigation, search
MediaWiki extensions manual - list
Crystal Clear action run.png
LogCalculator

Release status: beta

Implementation Variable
Description A basic javascript calculator with logarithm button
Author(s) Tommy Ekola
Last version 0.9
MediaWiki tested with 1.11.1
License GPL
Download Here
Example [1]
Hooks used
LanguageGetMagic

ParserGetVariableValueSwitch
MagicWordwgVariableIDs

LogCalculator is an extension that defines a variable {{LOGCALCULATOR}} which, when used in a wikitext, includes a simple javascript calculator with a logarithm button on the resulting page.


Contents

[edit] Example

If you include the following line in a wikitext

{{LOGCALCULATOR}}

then a javascript calculator is included in the resulting page:

Logcalculator.png

(This is just an image. On the real calculator you can click on the keys.)


[edit] Installation

[edit] Save the code

Create the directory $IP/extensions/LogCalculator and save the code in the files LogCalculator.php, LogCalculator.js and LogCalculator.css, respectively, in that directory.

[edit] Configure the extension

Add the following lines at the end of the file $IP/LocalSettings.php

$wgLogCalculatorRoot = "/wiki/extensions/LogCalculator";
include_once( $IP . "/extensions/LogCalculator/LogCalculator.php" );

where

  • $wgLogCalculatorRoot is the path to the javascript file LogCalculator.js and style file LogCalculator.css.


[edit] The code

[edit] LogCalculator.php

<?php
 
/* 
 * @author Tommy Ekola
 * @copyright © 2008 by Tommy Ekola (tek@kth.se)
 * @licence GNU General Public Licence 2 or later
 */
 
 
if (! defined( 'MEDIAWIKI' ) ) {
        echo( "This is an extension to the MediaWiki package and cannot be run standalone.\n" );
        die( -1 );
}
 
 
// Extension information
 
$wgExtensionCredits['other'][] = array(
        'name'        => 'LogCalculator',
        'description' => 'A basic javascript calculator with logarithm button',
        'version'     => '0.9',
        'author'      => 'Tommy Ekola');
 
 
// Configuration
 
if(! isset($wgLogCalculatorRoot) ) {
        echo( "The root directory of LogCalculator is not specified. Make sure \$wgLogCalculatorRoot is set in $IP/LocalSettings.php" );
        die( -1 );
}
 
 
// Include javascript and style files
 
$wgExtensionFunctions[] = 'wfLogCalculatorSetup';
 
function wfLogCalculatorSetup() {
        global $wgOut, $wgLogCalculatorRoot, $wgJsMimeType;
 
        $wgOut->addScript( '<link rel="stylesheet" type="text/css" '
                . 'href="' . $wgLogCalculatorRoot . '/LogCalculator.css" />' );
 
        $wgOut->addScript( '<script type="' . $wgJsMimeType . '" src="'
                . $wgLogCalculatorRoot . '/LogCalculator.js"></script>' );
}
 
 
// Define {{LOGCALCULATOR}}
 
define( 'LOGCALCULATOR', 'logcalculator' );
 
$wgHooks['LanguageGetMagic'][] = 'wfLogCalculator_Magic';
 
function wfLogCalculator_Magic( &$MagicWords, &$langCode ) {
  $MagicWords[LOGCALCULATOR] = array( 0, 'log-calculator', 'logcalculator' );
        return true;
}
 
$wgHooks['ParserGetVariableValueSwitch'][] = 'wfLogCalculatorVarAssign';
 
function wfLogCalculatorVarAssign( &$parser, &$cache, &$magicWordId, &$ret ) {
        if( $magicWordId == LOGCALCULATOR ) {
                $ret = "<div>";
                $ret .= "<form onsubmit=\"keyclick('='); return false;\">";
                $ret .= "<div id=\"logcalculator\" class=\"drag\">";
                $ret .= "<div class=\"logcalculatorcontainer\">";
                $ret .= "<input type=\"text\" name=\"input\" size=\"16\" id=\"result\" value=\"0\"><br/>";
                $ret .= "<input type=\"button\" value=\"AC\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"C\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"LN\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"×\" onclick=\"keyclick(this.value)\"><br/>";
                $ret .= "<input type=\"button\" value=\"7\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"8\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"9\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"÷\" onclick=\"keyclick(this.value)\"><br/>";
                $ret .= "<input type=\"button\" value=\"4\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"5\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"6\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"+\" onclick=\"keyclick(this.value)\"><br/>";
                $ret .= "<input type=\"button\" value=\"1\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"2\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"3\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"-\" onclick=\"keyclick(this.value)\"><br/>";
                $ret .= "<input type=\"button\" value=\"+/-\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"0\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\".\" onclick=\"keyclick(this.value)\"><input type=\"button\" value=\"=\" onclick=\"keyclick(this.value)\">";
                $ret .= "</div>";
                $ret .= "</div>";
                $ret .= "</form>";
                $ret .= "</div>";
        }
        return true;
}
 
$wgHooks['MagicWordwgVariableIDs'][] = 'wfLogCalculatorDeclareVarIds';
 
function wfLogCalculatorDeclareVarIds( &$aCustomVariableIds ) {
        $aCustomVariableIds[] = LOGCALCULATOR;
        return true;
}

[edit] LogCalculator.js

function Stack(initial_stack,initial_opstack) {
  this.stack = new Array();
  this.opstack = new Array();
};
 
Stack.prototype.top = function() {
  return this.stack[this.stack.length-1];
};
 
Stack.prototype.topop = function() {
  return this.opstack[this.opstack.length-1];
};
 
Stack.prototype.pop = function() {
  return this.stack.pop();
};
 
Stack.prototype.popop = function() {
  return this.opstack.pop();
};
 
Stack.prototype.push = function(a) {
  return this.stack.push(a);
};
 
Stack.prototype.pushop = function(a) {
  return this.opstack.push(a);
};
 
Stack.prototype.clear = function() {
  this.stack = [];
  this.opstack = [];
};
 
Stack.prototype.reduce = function() {
  while(this.opstack.length>0) {
    op = this.popop();
    this.op(op);
  }
};
 
Stack.prototype.empty = function() {
  if (this.stack.length==0)
    return true;
  else
    return false;
};
 
Stack.prototype.opempty = function() {
  if (this.opstack.length==0)
    return true;
  else
    return false;
};
 
Stack.prototype.op = function(op) {
  switch(op) {
    case '+':
      a = this.pop();
      b = this.pop();
      try {
        c = parseFloat(a)+parseFloat(b);
      }
      catch(err) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;
      }
      if(!isFinite(c)) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;        
      }
      this.push(c.toString());
      break;
    case '-':
      a = this.pop();
      b = this.pop();
      try {
        c = parseFloat(b)-parseFloat(a);
      }
      catch(err) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;
      }
      if(!isFinite(c)) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;        
      }
      this.push(c.toString());
      break;
    case '×':
      a = this.pop();
      b = this.pop();
      try {
        c = parseFloat(a)*parseFloat(b);
      }
      catch(err) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;
      }
      if(!isFinite(c)) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;        
      } 
      this.push(c.toString());
      break;
    case '÷':
      a = this.pop();
      b = this.pop();
      try {
        c = parseFloat(b)/parseFloat(a);
      }
      catch(err) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;
      }
      if(!isFinite(c)) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;        
      }
      this.push(c.toString());
      break;
    case '+/-':
      a = this.pop();
      try {
        b = -parseFloat(a);
      }
      catch(err) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;        
      }
      if(!isFinite(b)) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;        
      }
      this.push(b.toString());
      break;
    case 'LN':
      a = this.pop();
      try {
        b = Math.log(parseFloat(a));
      }
      catch(err) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;        
      }
      if(!isFinite(b)) {
        this.clear();
        this.push('Error');
        state = 'error_state';
        break;        
      }
      this.push(b.toString());
      break;
    default:
      throw('Unknown operator ' + op + '.');
      break;
  }
};
 
function keyclick(key) {
  key = key.toString();  
  switch(state) {
    case 'empty_state':
      switch(key) {
        case '0':
          buffer = key;
          show(buffer);
          state = 'zero_state';
          break;
        case '1': case '2': case '3': case '4': case '5': case '6':
        case '7': case '8': case '9':
          buffer = key;
          show(buffer);
          if (stack.opempty())
            stack.clear();
          state = 'integer_state';
          break;
        case '.':
          buffer = '0.';
          show(buffer);
          if (stack.opempty())
            stack.clear();
          state = 'float_state';
          break;
        case '=':
          stack.reduce();
          show(stack.top());
          break;
        case '+': case '-':
          stack.reduce();
          show(stack.top());
          stack.pushop(key);
          break;
        case '×': case '÷':
          if (stack.topop()=='×' || stack.topop()=='÷')
            stack.reduce();
          else if(stack.stack.length!=stack.opstack.length+1) {
            stack.reduce();
          }
          show(stack.top());
          stack.pushop(key);
          break;
        case '+/-': case 'LN':
          if(stack.stack.length!=stack.opstack.length+1) {
            stack.reduce();
          }
          stack.op(key);
          show(stack.top());
          break;
        case 'C':
          buffer = '0';
          show(buffer);
          stack.clear();
          state = 'zero_state';
          break;
        case 'AC':
          buffer = '0';
          show(buffer);
          stack.clear();
          state = 'zero_state';
          break;
        default:
          break;
      }
      break;
    case 'zero_state':
      switch(key) {
        case '0':
          break;
        case '1': case '2': case '3': case '4': case '5': case '6':
        case '7': case '8': case '9':
          buffer = key;
          show(buffer);
          state = 'integer_state';
          break;
        case '.':
          buffer = '0.';
          show(buffer);
          state = 'float_state';
          break;
        case '=':
          stack.push(buffer);
          stack.reduce();
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case '+': case '-':
          stack.push(buffer);
          stack.reduce();
          stack.pushop(key);
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case '×': case '÷':
          stack.push(buffer);
          if(stack.topop()=='×' || stack.topop()=='÷') {
            stack.reduce();
          }
          stack.pushop(key);
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case '+/-':
          break;
        case 'LN':
          stack.push(buffer);
          stack.op(key);
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case 'C':
          break;
        case 'AC':
          stack.clear();
          break;
        default:
          break;        
      }
      break;
    case 'integer_state':
      switch(key) {
        case '0': case '1': case '2': case '3': case '4': case '5':
        case '6': case '7': case '8': case '9':
          buffer = buffer + key;
          show(buffer);
          break;
        case '.':
          buffer = buffer + '.';
          show(buffer);
          state = 'float_state';
          break;
        case '=':
          stack.push(buffer);
          stack.reduce();
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case '+': case '-':
          stack.push(buffer);
          stack.reduce();
          stack.pushop(key);
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case '×': case '÷':
          stack.push(buffer);
          if(stack.topop()=='×' || stack.topop()=='÷') {
            stack.reduce();
          }
          stack.pushop(key);
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case '+/-':
          a = parseInt(buffer);
          a = -a;
          buffer = a.toString();
          show(buffer);
          break;
        case 'LN':
          stack.push(buffer);
          stack.op(key);
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case 'C':
          buffer = '0';
          show(buffer);
          state = 'zero_state';
          break;
        case 'AC':
          buffer = '0';
          show(buffer);
          stack.clear();
          state = 'zero_state';
        default:
          break;
      }
      break;
    case 'float_state':
      switch(key) {
        case '0': case '1': case '2': case '3': case '4': case '5':
        case '6': case '7': case '8': case '9':
          buffer = buffer + key;
          show(buffer);
          break;
        case '.':
          break;
        case '=':
          stack.push(buffer);
          stack.reduce();
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case '+': case '-':
          stack.push(buffer);
          stack.reduce();
          stack.pushop(key);
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case '×': case '÷':
          stack.push(buffer);
          if(stack.topop()=='×' || stack.topop()=='÷') {
            stack.reduce();
          }
          stack.pushop(key);
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case '+/-':
          a = parseFloat(buffer);
          a = -a;
          buffer = a.toString();
          show(buffer);
          break;
        case 'LN':
          stack.push(buffer);
          stack.op(key);
          show(stack.top());
          if (state!="error_state") state = 'empty_state';
          break;
        case 'C':
          buffer = '0';
          show(buffer);
          state = 'zero_state';
          break;
        case 'AC':
          buffer = '0';
          show(buffer);
          stack.clear();
          state = 'zero_state';
        default:
          break;        
      }
      break;
    case 'error_state':
      switch(key) {
        case 'AC': case 'C':
          show('0');
          stack.clear();
          buffer = '0';
          state = 'zero_state';
          break;
        default:
          break;
      }
      break;
    default:
      break;
  }
};
 
function show(num) {
  num = num.toString();
  if((num=="NaN") || (num=="Infinity") || (num=="-Infinity")) {
    show('Error');
  }
  else {
    document.getElementById("result").value = num;
  }
}
 
var stack = new Stack([],[]);
var buffer = "0";
var state = 'zero_state';

[edit] LogCalculator.css

#logcalculator {
  filter:alpha(opacity=85);
  -moz-opacity:.85;
  opacity:.85;
}
 
#logcalculator {
  background-color: #F4F4F4;
  width: 190px;
}
 
.logcalculatorcontainer {
  padding: 5px;
  border-top: 1px solid #C0C0C0;
  border-left: 1px solid #C0C0C0;
  border-right: 2px outset #C0C0C0;
  border-bottom: 2px outset #C0C0C0;
}
 
#logcalculator #control {
  text-align: right;
}
 
#logcalculator input {
  width: 40px;
  height: 30px;
  margin: 2px;
  background-color: #FFF;
  font-family: verdana,ariel,helvetica,sans-serif;
  font-size: 0.95em;
  border: 1px solid #C0C0C0;
  cursor: hand;
  cursor: pointer;
}
 
#logcalculator img {
  border: 0;
}
 
#logcalculator #result {
  width: 166px;
  text-align: right;
  font-family: courier new, monospace;
  font-size: 1.3em;
  padding: 2px;
  cursor: text;
}
 
#logcalculator .operator {
  color: #999;
  font-weight: bold;
  background-color: #DDD;
}
 
#logcalculator .equals {
  color: #FFF;
  font-weight: bold;
  background-color: #336699;
}


[edit] Bugs and future improvements

  • It is not possible to include two copies of the calculator on the same page.
  • Numbers sometimes overflow the result window of the calculator.
Personal tools
Namespaces

Variants
Actions
Navigation
Support
Download
Development
Communication
Print/export
Toolbox