Category Archives: js

Refactoring of the week

RefactorMyCode.com has now been (publicly) live for a week. It was amazing! Who would have though a simple site like this could attract more then 100 000 visits in a week ?

But what surprises me the most is how much the community is alive and healty. I haven’t had any spam really yet, well maybe 3-4. And 3 other posts from people saying “you suck”. But most of the time when people get upset in there, they apologies right after and people are really thankful when they get help, amazing! How different from forums and websites where people tell you to search or RTMF before thinking about helping you ? But RefactorMyCode is not for support anyway, it’s for challenging the mind ratter then fixing problems. Hackers like solving small well defined problems, I think that’s why the site is so successful.

To celebrate RmC first week anniversary I’d like to highlight one of the refactoring from this week.

Chris Lamothe submitted this one: Beautify JS Date to how recently the event occured.

This adds a .when() method to Date() object allowing you do things such as “Posted ” + someDate.when() + ” ago.” and get output such as “Posted 10 minutes ago.” I’ve refactored it twice but would like to see it even smaller.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/***
* Beautify date to how recent the event occurred compared to now.
* Some examples:  1 second, 4 hours, 10 days, 1 year.
*	Example:
*   var oneFiftyMin = new Date(new Date().getTime() - 60000 * 150);
*   alert(oneFiftyMin.when()); // will display "2 hours"
*
*  Handy for things like "Item posted " + someDate.when() + " ago."
*/
Date.prototype.when = function() {

	var diff = new Date().getTime() - this.getTime();
	var when; // our return value

	//TODO:  what if the time is in the future? 
	//if (diff < 0) throw new Error ("Date is in future, check timezone?";

	//one or more of these will be non-zero, but we only care about the biggest one (in scale of time)
  var secondsOld = Math.floor(diff / 1000);
  var minutesOld = Math.floor(diff / 60000);
  var hoursOld   = Math.floor(diff / 3600000);
  var daysOld    = Math.floor(diff / 86400000);
	var monthsOld  = Math.floor(diff / 2592000000);
	var yearsOld   = Math.floor(diff / (2592000000 * 12));  //your content is old!

	//determine which value is non-zero and assign appropriate text
  if (yearsOld > 0) {
		when = yearsOld + " year";
  }
  else if (monthsOld > 0) {
		when = monthsOld + " month";
  }
  else if (daysOld > 0) {
		when = daysOld + " day";
  }
  else if (hoursOld > 0) {
		when = hoursOld + " hour";
  }
  else if (minutesOld > 0) {
		when = minutesOld + " minute";
  }
  else {
		when = Math.round(diff /1000) + " second";
	}

	//add plural if necessary
	return (0 == when.indexOf("1 ")) ? when : when + "s";
} 

Small_logo

With the help of typefreak, Andre Steenveld and Tomasz Kołodziejski they finally end up with this code, that does exactly the same thing in less then one-third the line count.

1
2
3
4
5
6
7
8
9
10
11
12
Date.prototype.when = function(){
        var diff = (new Date() - this)/1e3,
        u = [ "second", "minute", "hour", "day", "weeks", "month", "year" ],
        s = [ 1, 60, 60, 24, 7, 4.333, 12, 1e9], // again fast hack - 1e9
        i=0;

        for(;;i++){
                if((diff/=s[i])<1){
                        return ~~(diff*=s[i])+' '+u[i-1]+(diff>1?'s':0);
                }
        }
}

Small_logo

What can you say ? WOW !

Thanks to all people who’ve taken the time to write me an email to report a bug, suggestion or simply to say thanks, it means a lot to me!

Advertisements

7 Comments

Filed under js, refactormycode

Ruby to Javascript in 100 LOC, is it possible ?

The goal of this tutorial is to show you how, with Ruby, we can turn this:

if ($('a').value == 'a') {
  doThis(true, 'maybe');
  andThis($('a').innerHTML);
}
var MyClass = Class.create();
MyClass.prototype = {
  myMethod: function(arg1, arg2) {
    cool();
  },
  anotherOne: function() {
    say('hi');
  },
};

Into Ruby code, with a 100 lines Ruby script.

Go grab a beer / coffee / Guru / water / Asian pear / Pad Thai and get ready !

# First we build a base Page class that will handle the common statement and hold the output.
# This is pretty simple, as you can see, we only output text.
class Page
  def initialize
    @output = ''
  end
  
  def if(condition)
    self << "if (#{condition}) {"
    yield self
    self << "}"
  end
  
  def def(method, *args)
    self << "#{method}: function(#{args * ', '}) {"
    yield self
    self << "},"
  end
  
  def class(name, &block)
    self << "var #{name} = Class.create();"
    self << "#{name}.prototype = {"
    yield self
    self << "};"
  end
  
  def <<(output)
    @output << output << "\n"
  end
  
  def to_s
    @output
  end
end

page = Page.new

# Using those methods looks pretty cool
# but we still need to write some js code.
page.if "$('a').value == 'a'" do
  page << "doThis(true, 'maybe');"
  page << "andThis($('a').innerHTML);"
end
page.class :MyClass do
  page.def 'myMethod', :arg1, :arg2 do
    page << "cool();"
  end
  page.def 'anotherOne' do
    page << "say('hi');"
  end
end

puts page

# Outputs
if ($('a').value == 'a') {
doThis(true, 'maybe');
andThis($('a').innerHTML);
}
var MyClass = Class.create();
MyClass.prototype = {
myMethod: function(arg1, arg2) {
cool();
},
anotherOne: function() {
say('hi');
},
};

# Iteration 2 - Make the outputed code beautiful

# Lets add some helper methods to all the objects
# so we can make beautiful Ruby code output as
# beautiful Javascript.
class Object
  # This method will allow to use Ruby naming convention
  # to declare Javascript methods.
  # Converting my_method to myMethod
  def to_js_method
    to_s.gsub(/_(\w)/) { $1.upcase }
  end
  
  # This one will convert an object to it's Javascript
  # equivalent. This is pretty basic here. Wrap string in '
  # or outputing if any other type.
  def to_js_arg
    self.is_a?(String) ? "'#{self}'" : self
  end
end

class Page
  def initialize
    @output = ''
    @indent = 0
  end
  
  # We make the outputed code a little more pretty
  # by indenting it! See the indented method bellow.
  def if(condition)
    self << "if (#{condition}) {"
    indented { yield self }
    self << "}"
  end
  
  ...
  
  # If an unexisting method is called, output it
  # as a Javascript method call.
  def method_missing(method, *args)
    self << "#{method.to_js_method}(#{args.collect { |arg| arg.to_js_arg } * ', '});"
  end
  
  def <<(output)
    @output << '  ' * @indent << output << "\n"
  end
  
  private
    def indented
      @indent += 1
      yield
      @indent -= 1
    end
end

page = Page.new

page.if "$('a').value == 'a'" do
  # As you can see, we use the method_missing magic
  # to create method calls
  page.do_this true, 'maybe'
  page.and_this "$('a').innerHTML"
end
page.class :MyClass do
  page.def :my_method, :arg1, :arg2 do
    page.cool
  end
  page.def :another_one do
    page.say 'hi'
  end
end

puts page

# Nice camelized method names, clearly indented = sweet!
if ($('a').value == 'a') {
  doThis(true, 'maybe');
  andThis('$('a').innerHTML');
}
var MyClass = Class.create();
MyClass.prototype = {
  myMethod: function(arg1, arg2) {
    cool();
  },
  anotherOne: function() {
    say('hi');
  },
};

# Iteration 3 - converting Ruby expressions to Javascript

# Now lets take care of that if statement.
# In Ruby, even operators are methods. We can take advantage of this
# and override those and output what we want!

# This module will override all comparison operators
# and output a string representation of the expression.
# So calling a == 'a' will return the string "a == 'a'"
module JSComparable
  def self.included(base)
    base.class_eval do
      %w(== < > <= >=).each do |operator|
        define_method operator do |other|
          [self.to_js_arg, operator, other.to_js_arg] * ' '
        end
      end
    end
  end
end

# We need to defined a simple class in which we will
# override those operators.
class Element
  attr_reader :id
  include JSComparable
  
  def initialize(id)
    @id = id
  end
  
  def to_s
    "$('#{id}')"
  end
end

# Since we're lazy, we'd like to type E[:a] ratter then Element.new(:a)
module E
  def self.[](id)
    Element.new id
  end
end

# All this allows us to rewrite this:
page.if "$('a') == 'a'" do
# into this:
page.if E['a'] == 'a' do
# Pure Ruby, how cool ?

###############
# Iteration 4 #
###############
# Now one line is still pretty annoying:
#   page.and_this "$('a').innerHTML"
# To turn this into Ruby code we can use a little
# method_missing magic again.

# We'll create a small class that will handle
# the calls to methods on element.
# When rendered, parent.method is outputed.
class MethodProxy
  include JSComparable
  
  def initialize(parent, name)
    @parent = parent
    @name = name
  end
  
  def to_s
    [@parent, @name.to_js_method] * '.'
  end
end

# Add a simple hook into Element
# so any unknow method calls are rendered as
# Javascript method calls.
class Element
  ...
  
  def method_missing(method, *args)
    MethodProxy.new(self, method)
  end
end

# All this allows us to come to the almost perfect:
page.if E['a'].value == 'a' do
  page.do_this true, 'maybe'
  page.and_this E[:a].innerHTML
end
page.class :MyClass do
  page.def :my_method, :arg1, :arg2 do
    page.cool
  end
  page.def :another_one do
    page.say 'hi'
  end
end

# Iteration 5 - Final touch, making this a real language !

# The 'page.' thing is pretty annoying I know!
# What's the simpless thing we could do to remove this ?

page = Page.new

code = File.read(ARGV[0])
code.gsub! /(if|class|def) (.*)$/, 'page.\1 \2 do'
eval code

puts page

# Now place this in a seperate file:
if E[:a].value == 'a'
  page.do_this(true, 'maybe')
  page.and_this(E[:a].innerHTML)
end

class :MyClass
  def :my_method, :arg1, :arg2
    page.cool
  end
  def :another_one
    page.say('hi')
  end
end

# And run with: ruby js.rb javascript.rjs
# and you'll get:

if ($('a').value == 'a') {
  doThis(true, 'maybe');
  andThis($('a').innerHTML);
}
var MyClass = Class.create();
MyClass.prototype = {
  myMethod: function(arg1, arg2) {
    cool();
  },
  anotherOne: function() {
    say('hi');
  },
};

If you’re serious about this, I’d suggest you take a look at ParseTree which allows you to explore the parse tree of some Ruby code.

I hope this helps anyone understand better some of Ruby meta-programming capability, beauty and power!

Ruby, I love you!

Amen

Get the full script here (rename it to js.rb)

11 Comments

Filed under js, ruby, tutorial

Metaprogramming: why dynamic languages matters

Reading Adam’s excellent
Javascript metaprogramming presentation, I’m realizing how much constraining static languages can be. Even tough type checking can help you find some errors before they occur, metaprogramming and DSL makes you write code that works by removing ambiguity. Chances are if you don’t have a clue what the code you just wrote is doing, it will break, explode, melt and smell.

Leave a comment

Filed under js, links

Making InPlaceEditor more usable

Scriptaculous InPlaceEditor is a great tool to ligthen a web page with editing needs. But it’s hard for a neophyte to guess that he has to click on it to edit. Unless you move your mouse over it you wont know!

Here’s a little tweek that uses a link to launch the edit mode of an InPlaceEditor.


<span id="my_text">My text</span>
<a href="javascript:void(0);" id="my_text_edit">edit</a>
<script type="text/javascript">
	var my_text_editor = new Ajax.InPlaceEditor('my_text',
		'your/url/to/edit', {
		externalControl:'my_text_edit',
		highlightcolor: 'transparent',
		clickToEditText: ''
	});
	$('my_text_edit').onclick = function(){
		my_text_editor.enterEditMode();
	}
	my_text_editor.dispose();
</script>

Leave a comment

Filed under js

Javascript OOP made simple

Javascript is a really powerful tool, and you should master it if you wanna be part of that web 2.0 wave. But when you start to get a lot of code it soon starts to smell. Unless…

Try this : Prototype, if your not yet using it or another Javascript framework.

You’ll then be able to give your code a more clean structure, like classes:


var MyClass = Class.create();
MyClass.prototype = {
  initialize: function(first, second) {
    // called when newing an instance
  },
  myMethod: function(args) {
    // woohoo a class method!
  }
}

Classes are defined in an hash with a referenced function.

Now split your hanging javascript function in classes or namespace:


var MyNamespace = {
  myFunction: function() {
    
  }
};

You can then refactor all your DOM events management with: Event.observe. No more onclick="...", just simple, clean code, that works on “all” browsers.

One note, if you’re hooking an event to an instance method, you sould bind the function to the current instance:


Event.observe('mybutton', 'click', this.onClick.bind(this));

Magic!

1 Comment

Filed under js