Monthly Archives: March 2013

JavaScript – Lineart

Just re-implemented the lineart for learning JavaScript and Jasmine. Though I’m not yet feeling right on the slew of “function()” and semicolons, JavaScript’s versatality these days is tempting enough to try. Maybe I need more practice. Should try CoffeeScript? hmm, not sure.

The following is working example and codes.

http://jsdo.it/parroty/bf5T?lang=en

HTML

<!DOCTYPE html>
<head>
  <script type="text/javascript" charset="utf-8" src="src/Line.js"></script>
</head>
<body onLoad="init()" style="background-color:#000000">
  <canvas id="sample1" width="100%" height="100%" style="border:solid 1px;background-color:#000000"></canvas>
</body>
</html>

JavaScript

var canvas;
var ctx;
var points;
var colors;

//------- Random Class -------
var Random = function() {};
Random.generate = function(range) {
  return Math.floor(Math.random() * range);
}

//------- Color Class -------
var Color = function(r, g, b) {
  this.r  = (r === undefined ? 0 : r);
  this.g  = (g === undefined ? 0 : g);
  this.b  = (b === undefined ? 0 : b);
  this.sr = 0;
  this.sg = 0;
  this.sb = 0;
}

Color.SPEED_BASE    = 2;
Color.SPEED_VARIANT = 4;

Color.prototype = {
  clone: function() {
    var c = new Color(this.r, this.g, this.b);
    c.setSpeeds(this.sr, this.sg, this.sb);
    return c;
  },

  rgb: function() {
    return [this.r, this.g, this.b];
  },

  speeds: function() {
    return [this.sr, this.sg, this.sb];
  },

  setSpeeds: function(sr, sg, sb) {
    this.sr = sr;
    this.sg = sg;
    this.sb = sb;
  },

  move: function() {
    var r = Mover.update(this.r, this.sr, 256);
    var g = Mover.update(this.g, this.sg, 256);
    var b = Mover.update(this.b, this.sb, 256);

    this.r = r[0]; this.sr = r[1];
    this.g = g[0]; this.sg = g[1];
    this.b = b[0]; this.sb = b[1];
  },

  to_s: function() {
    var r = Math.floor(this.r);
    var g = Math.floor(this.g);
    var b = Math.floor(this.b);
    return "rgb(" + r + "," + g + "," + b + ")";
  }
}

//------- Point Class -------
var Point = function(x, y, dx, dy) {
  this.x  = (x  === undefined ? 0 : x);
  this.y  = (y  === undefined ? 0 : y);
  this.dx = (dx === undefined ? 0 : dx);
  this.dy = (dy === undefined ? 0 : dy);
};

Point.SPEED_BASE    = 10;
Point.SPEED_VARIANT = 10;

Point.prototype = {
  nextPosition: function() {
    var x = Mover.update(this.x, this.dx, Lineart.screenWidth);
    var y = Mover.update(this.y, this.dy, Lineart.screenHeight);

    return new Point(x[0], y[0], x[1], y[1]);
  },

  setX: function(val) {
    if(val >= 0 && val <= Lineart.screenWidth) {
      this.x = val;
    }
    else {
      var msg = "invalid x value : " + val + ". It should be between 0 and " + Lineart.screenWidth;
      throw new Error(msg);
    }
  },

  setY: function(val) {
    if(val >= 0 && val <= Lineart.screenHeight) {
      this.y = val;
    }
    else {
      var msg = "invalid y value : " + val + ". It should be between 0 and " + Lineart.screenHeight;
      throw new Error(msg);
    }
  }
}

//------- Polygon Class -------
var Polygon = function(points, color) {
  if(color === undefined) {
    this.color = Generator.generateColor();
  }
  else {
    this.color = color;
  }

  if(points === undefined) {
    this.points = [];
    for(var i = 0; i < Lineart.POINTS_NUM; i++) {
      this.points.push(new Point());
    }
  }
  else {
    this.points = points;
  }
};

Polygon.prototype = {
  move: function() {
    this.points = this.points.map(function(item){
      return item.nextPosition();
    });

    this.color.move();
  }
}

//------- Mover Class -------
var Mover = function() {};
Mover.update = function(pos, diff, border) {
  pos += diff

  // when bounce at upper side
  if(pos > border) {
    pos = (border * 2) - pos;
    diff *= -1;
  }

  // when bounce at lower side
  if(pos < 0) {
    pos *= -1;
    diff *= -1;
  }

  return [pos, diff];
}

//------- Generator Class -------
var Generator = function() {};

Generator.generatePolygons = function(points, num) {
  var polygons = [];
  var color = Generator.generateColor();
  for(var i = 0; i < num; i++) {
    polygons.push(new Polygon(points, color));

    points = points.map(function(item) {
      return item.nextPosition();
    });
    color = Generator.generateNextColor(color);
  }
  return polygons;
}

Generator.generatePoints = function(xrange, yrange, num) {
  var points = [];
  for(var i = 0; i < num; i++) {
    points.push(
      new Point(
        Random.generate(xrange),
        Random.generate(yrange),
        Random.generate(Point.SPEED_VARIANT) + Point.SPEED_BASE,
        Random.generate(Point.SPEED_VARIANT) + Point.SPEED_BASE
      )
    );
  }

  return points;
}

Generator.generateColor = function() {
  var c = new Color(
    Random.generate(256),
    Random.generate(256),
    Random.generate(256));

  c.setSpeeds(
    Random.generate(Color.SPEED_VARIANT) + Color.SPEED_BASE,
    Random.generate(Color.SPEED_VARIANT) + Color.SPEED_BASE,
    Random.generate(Color.SPEED_VARIANT) + Color.SPEED_BASE);

  return c;
}

Generator.generateNextColor = function(color) {
  var newColor = color.clone();
  newColor.move();
  return newColor;
}

//------- Lineart Class -------
var Lineart = function(polygons) {
  var points = Generator.generatePoints(Lineart.SCREEN_WIDTH, Lineart.SCREEN_HEIGHT, Lineart.POINTS_NUM);
  this.polygons = Generator.generatePolygons(points, Lineart.POLYGON_NUM);
};

Lineart.POLYGON_NUM = 10;
Lineart.SCREEN_WIDTH = 320;
Lineart.SCREEN_HEIGHT = 240;
Lineart.POINTS_NUM = 4;

Lineart.prototype = {
  move: function() {
    for(var i = 0; i < this.polygons.length; i++) {
      this.polygons[i].move();
    }
  }
}

Lineart.reset = function() {
  this.screenWidth = Lineart.SCREEN_WIDTH;
  this.screenHeight = Lineart.SCREEN_HEIGHT;
  this.initialized = false;
}

Lineart.initScreenSize = function(width, height) {
  if(!this.initialized) {
    this.screenWidth  = width;
    this.screenHeight = height;
    this.initialized = true;
  }
  else {
      throw new UserException("it doesn't support changing screen size yet");
  }
}

//------- Logics -------
var lineart;
function init() {
  canvas = document.getElementById('sample1');
  if (canvas.getContext) {
    ctx = canvas.getContext('2d');
    ctx.canvas.width = window.innerWidth;
    ctx.canvas.height = window.innerHeight;

    Lineart.initScreenSize(
      window.innerWidth,
      window.innerHeight
    );
  }
  else {
    alert("failed to initialize canvas");
  }

  // initialize defaults points
  lineart = new Lineart();
  repeat_drawing();
}

function draw() {
  var polygons = lineart.polygons;
  for(var i = 0; i < polygons.length; i++) {
    var polygon = polygons[i];
    var color   = polygon.color.to_s();
    var points  = polygon.points;

    for(var j = 0; j < points.length; j++) {
      ctx.strokeStyle = color;
      ctx.beginPath();
      ctx.moveTo(points[j].x, points[j].y);
      ctx.lineTo(points[(j + 1) % Lineart.POINTS_NUM].x, points[(j + 1) % Lineart.POINTS_NUM].y);
      ctx.stroke();
    }
  }
}

function repeat_drawing() {
  ctx.clearRect(0, 0, Lineart.screenWidth, Lineart.screenHeight);
  draw();
  lineart.move();
  setTimeout(repeat_drawing, 50);
}

Jasime

var TEST_RANDOM_VALUES = 5;

describe("Lineart", function() {
  describe("Polygons", function() {
    describe("default behavior", function() {
      it("generates predefined number of polygon by default", function () {
        var la = new Lineart();
        expect(la.polygons.length).toEqual(Lineart.POLYGON_NUM);
      });

      it("sets screen width and height", function() {
        Lineart.initScreenSize(1280, 768);

        expect(Lineart.screenWidth).toEqual(1280);
        expect(Lineart.screenHeight).toEqual(768);
      });
    });

    describe("drawing", function() {
      beforeEach(function() {
        spyOn(Random, 'generate').andReturn(TEST_RANDOM_VALUES);
      });

      it("prepares for drawing", function() {
        var la = new Lineart();
        la.move();
        expect(la.polygons.length).toEqual(Lineart.POLYGON_NUM);
      });

      it("moves points", function() {
        var la = new Lineart();
        var beforePoints = la.polygons[0].points;
        la.move();
        var afterPoints = la.polygons[0].points;

        expect(beforePoints.length).toEqual(Lineart.POINTS_NUM);
        for(i in beforePoints) {
          expect(afterPoints[i].x).toEqual(
            beforePoints[i].x + TEST_RANDOM_VALUES + Point.SPEED_BASE);
        }
      });
    });

    describe("polygon", function() {
      it("has a color", function() {
        var polygons = new Lineart().polygons;
        for(i in polygons) {
          expect(polygons[i].color).toBeDefined();
        }
      });

      it("comprises of predefined points", function(){
        var polygons = new Lineart().polygons;
        for(i in polygons) {
          expect(polygons[i].points.length).toEqual(Lineart.POINTS_NUM);
        }
      });
    });
  });
});

describe("Point", function() {
  beforeEach(function() {
    Lineart.reset();
  });

  it("has location (x, y) and speed (dx, dy)", function(){
    var p = new Point(100, 200, 3, 4);
    expect([p.x, p.y, p.dx, p.dy]).toEqual([100, 200, 3, 4]);
  });

  describe("nextPosition", function(){
    it("returns normal next position", function(){
      var p = new Point(100, 200, 3, 4);
      var n = p.nextPosition();

      expect([n.x, n.y, n.dx, n.dy]).toEqual([103, 204, 3, 4]); // return next;
      expect([p.x, p.y, p.dx, p.dy]).toEqual([100, 200, 3, 4]); // keep original
    });

    it("returns bounced max-x position", function(){
      var p = new Point(Lineart.SCREEN_WIDTH - 7, 0, 10, 5)
      var n = p.nextPosition();

      expect([n.x, n.y, n.dx, n.dy]).toEqual([Lineart.SCREEN_WIDTH - 3, 5, -10, 5]);
    });

    it("returns bounced min-x position", function(){
      var p = new Point(3, 0, -10, 5)
      var n = p.nextPosition();

      expect([n.x, n.y, n.dx, n.dy]).toEqual([7, 5, 10, 5]);
    });
  });

  describe("validations", function() {
    it("should raise error if x value is invalid", function() {
      var point = new Point();
      expect(function() {
        point.setX(-1);
      }).toThrow();

      expect(function() {
        point.setX(Lineart.SCREEN_WIDTH + 1);
      }).toThrow();
    });

    it("should raise error if y value is invalid", function() {
      var point = new Point();
      expect(function() {
        point.setY(-1);
      }).toThrow();

      expect(function() {
        point.setY(Lineart.SCREEN_HEIGHT + 1);
      }).toThrow();
    });
  });
});

describe("Generator", function() {
  beforeEach(function() {
    spyOn(Random, 'generate').andReturn(TEST_RANDOM_VALUES);
  });

  describe("polygon and point", function(){
    var points;
    var polygons;
    beforeEach(function() {
      points = Generator.generatePoints(Lineart.SCREEN_WIDTH, Lineart.SCREEN_HEIGHT, Lineart.POINTS_NUM);
      polygons = Generator.generatePolygons(points, Lineart.POLYGON_NUM);
    });

    it("generates point locations", function() {
      expect(points.length).toEqual(Lineart.POINTS_NUM);
      for(i in points) {
        expect(points[i].x).toEqual(TEST_RANDOM_VALUES);
        expect(points[i].y).toEqual(TEST_RANDOM_VALUES);
        expect(points[i].dx).toEqual(TEST_RANDOM_VALUES + Point.SPEED_BASE);
        expect(points[i].dy).toEqual(TEST_RANDOM_VALUES + Point.SPEED_BASE);
      }
    });

    it("genetates polygon from points", function() {
      expect(polygons.length).toEqual(Lineart.POLYGON_NUM);
    });
  });

  describe("color", function() {
    it("generates random color", function() {
      var c = Generator.generateColor();
      expect(c.rgb()).toEqual([TEST_RANDOM_VALUES, TEST_RANDOM_VALUES, TEST_RANDOM_VALUES]);
    });

    it("generates next color", function() {
      var c1 = Generator.generateColor();
      var c2 = Generator.generateNextColor(c1);

      expect([c2.r, c2.g, c2.b]).toEqual([c1.r, c1.g, c1.b].map(function(item) {
        return item + TEST_RANDOM_VALUES + Color.SPEED_BASE;
      }));
    });

    it("has a change speed", function() {
      var c = Generator.generateColor();
      var val = TEST_RANDOM_VALUES + Color.SPEED_BASE;

      expect(c.speeds()).toEqual([val, val, val]);
    });
  })
});

describe("Color", function() {
  var c;
  beforeEach(function() {
    c = new Color(0, 100, 200);
  });

  it("has RGB value", function() {
    expect([c.r, c.g, c.b]).toEqual([0, 100, 200]);
  });

  it("can set speeds", function() {
    c.setSpeeds(1, 2, 3);

    expect([c.sr, c.sg, c.sb]).toEqual([1, 2, 3]);
  });

  it("transits based on its speed", function() {
    c.setSpeeds(1, 2, 3)
    c.move();

    expect(c.rgb()).toEqual([1, 102, 203]);
  });

  it("converts to string", function() {
    expect(c.to_s()).toEqual('rgb(0,100,200)');
  });
});

describe("Mover", function() {
  it("updates without bounce", function() {
    var ret = Mover.update(0, 3, 100);
    expect(ret).toEqual([3, 3]);
  });

  it("bounce at upper side", function() {
    var ret = Mover.update(99, 5, 100);
    expect(ret).toEqual([96, -5]);
  });

  it("bounce at lower side", function() {
    var ret = Mover.update(1, -5, 100);
    expect(ret).toEqual([4, 5]);
  });
});
Advertisements

Ruboto – Lineart

Some more trials on Ruboto. After hours of stuggle, I could finally make the old-fashioned lineart work on my Nexus7. It’s nice to have ruby on Android, though it was a little tougher than expected. Some impressions are,

  • [Features] Most of the Android API seems work. Though some tweak are required related to ruby’s grammer or syntax, most APIs are just accessbile through Java’s version of method/class signatures (camel cases). For basic classes, Ruboto also provides helper class/methods for more rubyish coding styles.
  • [Documentation] Official site wiki has some examples and it’s helpful. But, it’s limited to basic ones and doesn’t directly cover most of the usage on normal applications. It may be good to have more reference document of Ruboto features and helper functions.
  • [Development] Many of my struggles were coming from my lack of knowledge about Android development (My previous experience was “Hello World”). But, also ruby’s dynamic feature (and Java’s reflection at run-time) sometimes made it difficult to debug. “adb logcat” shows the stack trace in ruby-side too, and it was helpful to identfy the error location. However, as Java/Eclipse development has rich devopment/debugging features, there would be some rooms for improvements.

The following is my trial so far. I also put it into the GitHub (https://github.com/parroty/ruboto_lineart). Lack of class structure and use of global variables requires to be fixed.

ruboto_lineart

Activity and View Class

require 'ruboto/widget'
require 'lib/lineart'

java_import android.graphics.Paint

class LineartView < android.view.SurfaceView
  def initialize(context)
    super
    $initialized = false

    $holder = getHolder
    $holder.addCallback(Callback.new)
    $runnable = Runnable.new
  end

  class Runnable
    attr_writer :attached, :width, :height
    def run
      unless $initialized
        Lineart.init_screen_size(@width, @height)
        $lineart = Lineart.new

        $initialized = true
      end

      @attached = true
      while @attached
        canvas = $holder.lockCanvas

        if canvas
          canvas.drawColor(android.graphics.Color::BLACK)

          paint = Paint.new
          paint.setStrokeWidth(2.0)
          paint.setAntiAlias(true)

          $lineart.polygons.each do |polygon|
            color = polygon.color
            paint.setColor(android.graphics.Color.argb(255, color.r, color.g, color.b))

            points = polygon.points
            points.length.times do |i|
              canvas.drawLine(
                points[i].x, points[i].y,
                points[(i + 1) % Lineart::POINTS_NUM].x, points[(i + 1) % Lineart::POINTS_NUM].y,
                paint)
            end

          end
          $holder.unlockCanvasAndPost(canvas)
          $lineart.move
        end
      end
    end
  end

  class Callback
    def surfaceCreated(holder)
      rect = holder.getSurfaceFrame
      $runnable.width  = rect.right - rect.left
      $runnable.height = rect.bottom - rect.top

      @thread = java.lang.Thread.new($runnable)
      @thread.start
    end

    def surfaceChanged(holder, format, width, height)
      $runnable.width  = width
      $runnable.height = height
    end

    def surfaceDestroyed(holder)
      $runnable.attached = false
      while @thread.isAlive
      end
    end
  end
end

class LineartActivity
  def on_create(bundle)
    super
    set_title 'Lineart Sample'
    setContentView(LineartView.new(self))
  end
end

Libary Class

class Color
  SPEED_BASE    = 2
  SPEED_VARIANT = 4

  attr_accessor :r, :g, :b
  attr_accessor :sr, :sg, :sb

  def initialize(r = 0, g = 0, b = 0)
    self.r = r
    self.g = g
    self.b = b
  end

  def rgb
    [self.r, self.g, self.b]
  end

  def speeds
    [self.sr, self.sg, self.sb]
  end

  def set_speeds(sr, sg, sb)
    self.sr = sr
    self.sg = sg
    self.sb = sb
  end

  def move!
    self.r, self.sr = Mover.update(self.r, self.sr, 256)
    self.g, self.sg = Mover.update(self.g, self.sg, 256)
    self.b, self.sb = Mover.update(self.b, self.sb, 256)
    self
  end

  def to_s
    "rgb(#{self.r},#{self.g},#{self.b})"
  end
end

class Point
  SPEED_BASE    = 10
  SPEED_VARIANT = 10

  attr_reader :x, :y
  attr_accessor :dx, :dy

  def initialize(x = 0, y = 0, dx = 0, dy = 0)
    self.x = x
    self.y = y
    self.dx = dx
    self.dy = dy
  end

  def next_position
    nx, ndx = Mover.update(self.x, self.dx, Lineart.screen_width)
    ny, ndy = Mover.update(self.y, self.dy, Lineart.screen_height)
    Point.new(nx, ny, ndx, ndy)
  end

  def x=(val)
    if val >= 0 and val <= Lineart.screen_width
      @x = val
    else
      raise RuntimeError, "invalid x value : #{val}. It should be between 0 and #{Lineart.screen_width}"
    end
  end

  def y=(val)
    if val >= 0 and val <= Lineart.screen_height
      @y = val
    else
      raise RuntimeError, "invalid y value : #{val}. It should be between 0 and #{Lineart.screen_height}"
    end
  end
end

class Polygon
  attr_accessor :color, :points

  def initialize(points = nil, color = nil)
    self.color = color || Generator.generate_color
    self.points = points || [Point.new] * Lineart::POINTS_NUM
  end

  def move
    self.points = self.points.map do |points|
      points.next_position
    end

    self.color.move!
  end
end

class Mover
  def self.update(pos, speed, border)
    pos += speed

    # when bounce at upper side
    if pos > border
      pos = (border * 2) - pos
      speed *= -1
    end

    # when bounce at lower side
    if pos < 0
      pos *= -1
      speed *= -1
    end

    [pos, speed]
  end
end

class Generator
  def self.generate_polygons(points, num)
    polygons = []
    color = generate_color
    num.times do |i|
      polygons << Polygon.new(points, color)

      points = points.map {|p| p.next_position }
      color  = generate_next_color(color)
    end
    polygons
  end

  def self.generate_points(xrange, yrange, num)
    points = []
    num.times do |i|
      points << Point.new(
        rand(xrange),
        rand(yrange),
        rand(Point::SPEED_VARIANT) + Point::SPEED_BASE,
        rand(Point::SPEED_VARIANT) + Point::SPEED_BASE
      )
    end
    points
  end

  def self.generate_color
    c = Color.new(rand(256), rand(256), rand(256))
    c.set_speeds(rand(Color::SPEED_VARIANT) + Color::SPEED_BASE,
                 rand(Color::SPEED_VARIANT) + Color::SPEED_BASE,
                 rand(Color::SPEED_VARIANT) + Color::SPEED_BASE)
    c
  end

  def self.generate_next_color(color)
    new_color = color.clone
    new_color.move!
  end
end

class Lineart
  SCREEN_WIDTH  = 320
  SCREEN_HEIGHT = 240
  POLYGON_NUM   = 10
  POINTS_NUM    = 4

  attr_accessor :polygons

  class << self
    attr_reader :screen_width, :screen_height
  end

  def initialize
    points = Generator.generate_points(SCREEN_WIDTH, SCREEN_HEIGHT, POINTS_NUM)
    polygons = Generator.generate_polygons(points, POLYGON_NUM)
    self.polygons = polygons
  end

  def move
    polygons.each do |polygon|
      polygon.move
    end
  end

  def self.reset
    @screen_width  = SCREEN_WIDTH
    @screen_height = SCREEN_HEIGHT
    @initialized = false
  end
  self.reset

  def self.init_screen_size(width, height)
    unless @initialized
      @screen_width  = width
      @screen_height = height
      @initialized = true
    else
      raise "it doesn't support changing screen size yet"
    end
  end
end

Reading Professional JavaScript (2)

Additional notes while reading the “Professional JavaScript for Web Developers”.

It’s good to have fundamental JavaScript features in the first half of the book. Later part of the book is about HTML/JavaScript on browsers. It’s nicely organized and good reference information, but nowadays, many of the features are accessed through JavaScript frameworks like jQuery, Angular, Ember, etc, and might not be directly applicable.

Some notes from the first half.

Named function expressions

Named function expressions provides a name for functions, which allows to call it recursivly.

// recursion using named function expressions
var factorial = (function f(num) {
  if (num <= 1) {
    return 1;
  }
  else {
    return num * f(num - 1);
  }
});

console.log(factorial(1));  // => 1
console.log(factorial(5));  // => 120

Block Scope

JavaScript doesn’t have block-level scope, but wrapping with anonymous function provides a similar functionality. It allows isolating the variables from the global scope.

// wrapping with anonymous function
(function f() {
  var now = new Date();
  var m = now.getMonth() + 1;
  var d = now.getDate();
  console.log("Today is : " + m + "/" + d);
})();

console.log(now);  // ReferenceError: now is not defined

Ruboto

https://www.engineyard.com/video/16286183

Ruboto is an android development framework for ruby, and I’m playing around it for a while. The above video has a nice introduction though it’s a little old one.

My major motivation for this at the moment is to rejuvenate the Nexus7, which I impulsively got some time ago. I’ve tried PhoneGap (Cordova) which provides web-based application framework, but it didn’t fit well for me (I may prefer server-side one if it’s just a web-application). So, it’s my next trial.

https://github.com/ruboto/ruboto/wiki

I tried some using examples in the wiki, but I barely managed to write the following. Mostly, I keep seeing the “Unformtunately XXX has stopped” message. I may need more understanding of android frameworks, before pursuing further.

require 'ruboto/widget'
require 'ruboto/util/toast'

ruboto_import_widgets :Button, :LinearLayout, :TextView, :EditText, :RadioGroup, :RadioButton

class QuickStartActivity
  def on_create(bundle)
    super
    set_title 'Ruboto Sample'

    self.content_view =
      linear_layout :orientation => :vertical do
        @text_view = text_view :text => 'What would you like to order?'

        @radio_group = radio_group do
          radio_button :text => "Tuna",    :id => 0
          radio_button :text => "Trout",   :id => 1
          radio_button :text => "Salmon",  :id => 2
          radio_button :text => "Crab",    :id => 3
          radio_button :text => "Lobster", :id => 4
        end
        @quantity = edit_text :hint => "Quantity"
        button :text => "Place Order", :on_click_listener => proc { place_order }
      end
  rescue
    puts "Exception creating activity: #{$!}"
    puts $!.backtrace.join("\n")
  end

  private

  def place_order
    if @radio_group.checked_radio_button_id != -1
      selection = @radio_group.child_at(@radio_group.checked_radio_button_id).text
      toast "Order placed for #{@quantity.text} items of #{selection}"
    else
      toast "Please select item to order"
    end
  end
end

ruboto_sample

Reading Professional JavaScript (1)

Professional JavaScript for Web Developers (Wrox Professional Guides) [Paperback] – Amazon

Reading “Professional JavaScript for Web Developers”.

JavaScripts’ inheritance is a little tricky, as it doesn’t have “class” concept and every object is just a “function”. There’re many different patterns for managing inheritance, but frequently used one is the “combination inheritance”. It defines properties in the function instances, and defines methods in function prototypes as follows.

function SuperType(name) {
  // define properties for each SuperType instance
  this.name   = name;
  this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function() {  // define method for SuperType
  console.log(this.name);
};


function SubType(name, age) {
  SuperType.call(this, name);  // inherit properties from SuperType
  this.age = age;              // define property for each Subtype instance
}

SubType.prototype = new SuperType();    // inherit methods from SuperType
SubType.prototype.sayAge = function() { // define method for SubType
  console.log(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors);  // => [ 'red', 'blue', 'green', 'black' ]
instance1.sayName();            // => Nicholas
instance1.sayAge();             // => 29

var instance2 = new SubType("Greg", 27);
console.log(instance2.colors);  // => [ 'red', 'blue', 'green' ]
instance2.sayName();            // => Greg
instance2.sayAge();             // => 27

console.log(instance1.sayName == instance2.sayName);  // => true

If the methods are defined in the constructor, its definitions are created for each instances as follows (It’s not efficient and not makes sense).

function Person (name, age, job) {
  this.name = name;
  this.sayName = new Function("alert(this.name)");
}

var p1 = new Person();
var p2 = new Person();
console.log(p1.sayName == p2.sayName);  // => false

If the properties are defined in the prototpye, its definitions are shared among instances as follows (It’s not usually a expected behavior).

function Person() {
  Person.prototype.name    = "";
  Person.prototype.friends = [];

  Person.prototype.say = function() {
    console.log(
      "name = " + this.name + ", " +
      "friends = " + this.friends
    );
  }

  Person.prototype.sayFriends = function() {
    console.log(this.friends);
  }
}

var p1 = new Person();
var p2 = new Person();

p1.name = "Nicholas";
p1.friends.push("John");

p2.name = "Greg";
p2.friends.push("Bob");

p1.say();  // => name = Nicholas, friends = John,Bob
p2.say();  // => name = Greg, friends = John,Bob

AngularJS – Routing

This is a good tutorial video for AngularJS’s routing feature. It shows some good examples to simply list/create/edit objects.

By using routing, you can navigate a certain url-request to a specific controller and the html partial. It smoothly works, compared with server-side routings. This time, I just created a repo for storing the sample as it involves multiple files. This video uses node.js for serving html file, but I used Sinatra instead.

https://github.com/parroty/angularjs_samples

angular-sample

AngularJS – Dependency Injection


http://docs.angularjs.org/guide/di

Some more learning about AngularJS. Dependency injection is a major feature of Angular, and it’s where magics are happening. I still haven’t been able to get the clear understanding, but the above video helped some.

Angular’s dependency injection features allow to connect the controller with services through the argument-name listed in the constructor. Just aligning the argument-names with the defined service names makes Angular’s injector find the corresponding services. Services can be defined through Module.service, Module.factory, Module.value functions. Also, you can inject dependency for certain pre-defined services, like exception handler ($exceptionHandler). By hooking up with them, you can change the behavior as you like.

The followings are some trials I made.

http://jsfiddle.net/tbDCZ/

<html ng-app="app">
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular-resource.min.js"></script>
    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>

    <script>
      var app = angular.module('app', ['ngResource']);

      /* --------- Service Definitions --------*/
      // Using module.value
      app.value('sampleValue', 'Returned Sample Value');

      // Using module.factory
      app.factory('sampleFactory', function() {
        return {
          func : function() {
           return "Returned Sample Factory";
          }
        }
      });

      // Using module.service
      app.service('sampleService', function() {
        this.func = function() {
          return "Returned Sample Service";
        }
      });

      // Adding arguments (sampleXXX) allows to refer defined services, through argument names.
      app.directive('sample', function($parse, sampleValue, sampleFactory, sampleService) {
        return {
          link: function(scope, element, attr) {
            element.text('[sampleValue] is "' + sampleValue + '", ' +
                         '[sampleFactory] is "' + sampleFactory.func() + '", and ' +
                         '[sampleService] is "' + sampleService.func() + '".');
          }
        }
      });

      /* --------- Exception Handler ---------*/
      // using module.factory
      app.factory('$exceptionHandler', function($window) {
        return function(e) {
          //$window.alert(e);
          $("#error").text(e);  // using jQuery. Not sure how to access $scope.
        }
      });

      // using module.value
      /*app.value('$exceptionHandler', function(e) {
        alert(e);
      });*/

      function SampleCtrl($scope) {
        $scope.showError = function() {
          throw new Error('Sample Error Message');
        };
      }
    </script>

    <style type="text/css">
      .error {
        background-color: #fcc;
      }
    </style>
  </head>
  <body ng-controller='SampleCtrl'>
    <div>
      <div sample></div>
      <br/>
      <div ng-click="showError()">Click this message to throw an error.</div>
      <br/>
      <div id="error" class="error">Error message comes here.</div>
    </div>
  </body>
</html>

Ruby – Method and Messages

https://rubytapas.dpdcart.com/subscriber/post?id=45#files

Just subscribed rubytapas, and watching some more episodes. The above talks about method and messages (requires subscription).

As ruby can call a method without parenthesis, we cannot refer a function object in the format of “value = obj.method” (it actually calls the method, instead of just refering it). Ruby doesn’t have a concise way of storing function object in a variable. Instead, it requires “value = obj.method(:method_name)” or “value = Proc.new { do_something }” kind of expressions.

Semantically, ruby focuses on a “message” for external communication between objects rather than a procedural “method”. In the above episode, it’s summarized as follows.

  • A message is a name for a responsibility which a object may have
  • A method is a named, concrete piece of code that encodes one way a responsibility may be fulfilled.

Ruby objects/classes can be dynamically extended, and content of a method can be changed during the code execution. A method refers to specific code block when it’s refered, but a message (send method) just gets a response from the object.

class A
  def test
    puts "A:test"
  end
end

module B
  def test
    puts "B:test"
  end
end

a = A.new

a.send(:test)
  # => A:test
m1 = a.method(:test)

a.extend(B)

a.send(:test)
  # => B:test
m2 = a.method(:test)

m1.call
  # => A:test
m2.call
  # => B:test