Archive for the ‘Programming’ Category

jQuery Tag input

Saturday, January 24th, 2009

While implementing the function to “connect to friends” in our social network eventize.de, I thought about improving the handling of tags a bit.

The idea was to be able to select tags that were already in use (because it’s likely to use them over and over again). So I build a small jQuery plugin that does exactly this job:

tag_input_screenshot

What you basically need is a text field. I post my Rails code here:

<%= text_field_tag :tag_list, value,
  :class => 'text_input',
  :id => identifier_field,
  :onfocus => "jQuery.taginput.showTagList('##{identifier_field}', '##{identifier_popup}')",
  :onblur => "jQuery.taginput.hideTagList('##{identifier_field}', '##{identifier_popup}')",
  :oninput => "jQuery.taginput.setAlreadyUsedTags('##{identifier_field}', '##{identifier_popup}')",
  :autocomplete => 'off'
%>

And a bit HTML code to prebuild the popup (This could be done with jQuery, too):

Alright, go ahead and check it out for yourself. The code is pretty basic and there’s a lot of space for improvement. If you have any suggestions, post a comment!

jquerytaginput-10

Merging Rails’ i18n files

Thursday, January 8th, 2009

Lately I’ve been in a hassle to translate our application Quassum into English. My collegues continously modify the german i18n file for Rails, then the old structure of the other language files are left unmodified. Doing this by hand is close to impossible.

So what I came up with was a rake task that merges a give structure (YAML source file) into an existing target structure while overwriting every key that changed and adding new keys. Old keys and values are left as they are.

I didn’t find anything on the internet doing this task, so I hope this task will save you the time I spent last saturday. Good luck!

$KCODE = 'UTF8'
require 'ya2yaml'

namespace :quassum do
  desc "Copy all missing lang identifiers from [src].yml to [dest].yml"
  task :fill_lang do

    if ENV['src'].blank? or ENV['dest'].blank?
      puts "Give all parameters: src and dest. " +
        "E.g.: rake quassum:fill_lang src='de' dest='en'"
      exit
    end
    src_path = File.join(RAILS_ROOT,'config','locales', "#{ENV['src']}.yml")
    dest_path = File.join(RAILS_ROOT,'config','locales', "#{ENV['dest']}.yml")

    unless File.readable?(src_path)
      puts "File #{src_path} not readable!"
      exit
    end

    unless File.exist?(dest_path)
      puts "File #{dest_path} does not exist. Creating it..."
      File.new(dest_path, "w")
    end

    # We assume that the src file is correct...
    yaml_src = YAML::load_file(src_path)
    struct_src = yaml_src[ENV['src']]

    # ...but the src not necessarily. So, in case, we create a new lang file:
    @yaml_dest = YAML::load_file(dest_path)
    @yaml_dest ||= Hash.new
    @struct_dest = @yaml_dest[ENV['dest']]
    @struct_dest ||= Hash.new

    # merge all unknown changes to the dest struct:
    merge_recursively struct_src

    @yaml_dest[ENV['dest']] = @struct_dest
    File.open(dest_path, "w") do |file|
      file.puts @yaml_dest.ya2yaml
    end

    puts File.new(dest_path).read
    puts "Everything done."
  end

  #
  # SOME HELPER FUNCTIONS
  #
  def merge_recursively(pairs, parents = [])
    pairs.each_pair do |k,v|
      # copy the parents path and add the current element key:
      current_path = Array.new(parents) << k
      if v.is_a?(Hash)
        merge_recursively(v, current_path)
      else
        ensure_yaml_contains(@struct_dest, current_path, v.to_s)
      end
    end
  end

  def ensure_yaml_contains(element, path, val)
    #puts "    ensure_yaml_contains(#{element}, #{path}, #{val})"

    if path.length == 1
      if element[path.first].is_a?(Hash) and not val.blank?
        puts "Hash found instead of key. In favor of '#{val}' '#{element.to_s}' will be deleted!"
        element[path.first] = val
      end
      if element[path.first].nil?
        puts "Missing key '#{path.first}'! Pre-filled for editing with: '#{val}'. element was #{element.class}"
        element[path.first] = val + " [[TODO]]  "
      end
      # if none of these replaced anything - we're fine, the key exists!
      # :-)
    elsif path.length > 1
      # walk down if possible:
      if element[path.first].is_a?(Hash) and element.has_key?(path.first)
        ensure_yaml_contains(element[path.first], path[1..-1], val)
      else
        # We don't have a hash in the dest file, so we create the hash
        # and fill in all subsequent children:
        element[path.first] = Hash.new
        ensure_yaml_contains(element[path.first], path[1..-1], val)
      end
    else
      puts "REPLACING FAILED"
      # should stop here
    end
  end

end

Debug at the thought level

Wednesday, July 23rd, 2008

Have you evener wondered why so much code gets refactored again and again? Why there are several specialists living on this topic? If refactoring is about improving code, one could assume that’s quite a good thing.

Ever wondered why there is such a thing like refactoring? People seem to like implementing quickly and refactor afterwards. Ever wondered if we could do this in a single step? Or at least hack the stuff just one time?

The programmers I got in touch with hardly write something down before they implement. I also often feel like hacking when I first see the solution of a problem. But that’s the first step of getting yourself into trouble.

What we could do is write before we code. Write the solution. Not just the outline, the comfy part, write down the complex structure of the whole solution.

Writing things down shows you how sloppy your thinking is” says Leslie Lamport advertising the model-checking enabled TLA+, which is mainly designed for developing algorithms. I’d strongly agree but drag that idea to a more generic level:

Debug at the thought level! For every implementation, for every two classes you inherit, for every step you want to take in you career, for everything you feel too sure about. Write it down. See the output of you brain manifest and watch yourself thinking over it one more time.
If it’s not the IDE you commited your initial sloppyness to, you probably save a lot of time!

Een Interview met Ontwikkelar T.C.B.

Wednesday, January 10th, 2007

Hello Everybody.

First of all: A big Thank-you to Ton Haarmans! That’s actually the first article I wrote in a tech-book, so I’m quite proud.
Half a year ago he asked me to give him an interview about Ajax which he could use in his book about Javascript: “Javascript de basis”, you can buy the dutch book here.

Ton is a co-worker of mine in the transLucid project team, he designed the templates, did all the CSS work, created mockups etc… and he can write javascript and gives lessons in various topics.

Although I can’t read Dutch I assume that his book is an easy way to learn the basics of javascript and get good examples of what is possible with that technology. Anyone who already wrote that book shall feel free give me his thoughts; I’d print them here.

So far; have a nice start in 2007!

Till

P.S. I know that I don’t keep on blogging in the moment. The point is that I’m not sure how much I want to post about my private life etc. Besides my studies at the university of Kiel there aren’t so many interesting things about Australia or development at the moment.

YUI’s autocomplete working over xajax

Thursday, October 5th, 2006

Since had some trouble today combining those tho technologies but eventually found a way, I’d like to share my experience with you. This article requires some knowledge about xajax and the YUI; otherwise it’d be quite hard to understand.

Yahoo’s User Interface library (short: YUI) is a powerful and rich javascript toolset which makes several nifty javasript interactions possible; such as: animation, color fading, animated sliders and autocomplete and many more.
In our project transLucid we wanted to achieve the autocomplete mechanism with an autocomplete method: When connecting to a desired node we get suggestions appear so that we don’t have to type in the name completely.

To use as much existing code as possible we agreed on using the YUI toolset and the PHP Ajax framework xajax, which we’re using for the complete editmode in our application.

So now let’s have a look at the YUI autocomplete interface: [Documentation]. Our interest is rather the data sources than the UI and styles - we can copy them from an example later on and modify it. We have three possibilities for data sources:

  • YAHOO.widget.DS_JSArray(myArray); The data source that consists of an array. No way of querying our server over ajax here.
  • YAHOO.widget.DS_JSFunction(myFunction); The function given as a parameter here delivers a JS array. Again, we can specify the data delivering function but no way of letting a callback function triggered.
  • YAHOO.widget.DS_XHR(myServer, mySchema); The XmlHttpRequest datasource would be appropriate. Indeed, this function is a very flexible way of querying the server for XML, JSON or plain text data, putting the from ajax returned data back into our auto complete object. But here we would have to implement a complying function on our server that gives out the data. Since we use xajax we’d have to define another way of data output and could not use our common channel.

So - what to do? Let’s first check, what xajax does. It somehow hides the XML transport from the user/developer and gives him the ability to fill specific DIVs, call JS functions, redirect etc. So what we have in case of our autocomplete is following:

  • a JS function that is triggered when the autocomplete object (built by the YUI) decides to query data from the server.
  • a callback JS function that is called by the xajax with the function xajaxResponse::addScriptCall(). This function shall trigger the autocomplete object to display the results. we call it autocompleteCallback(aResponse). The aResponse is the actuall array (a PHP array converted to a JS array by xajax) containing the data.

Looking at the YUI file autocomplete/autocomplete.js we notice the class YAHOO.widget.DS_XHR already has a callback function in its function description of doQuery (that’s the function which is triggered every time the autocomplete box wants data):

YAHOO.widget.DS_XHR.prototype.doQuery = function(oCallbackFn, sQuery, oParent)

It is the oCallbackFn that we want to trigger. So now, we copy the whole DS_XHR class and make up our own derivation of a data source.

We need that code to initialize our own derivation:

YAHOO.widget.DS_XAJAXConnector = function()
{
this._init();
};</p><p>YAHOO.widget.DS_XAJAXConnector.prototype = new YAHOO.widget.DataSource();

Then we copy the function doQuery and inject our code:

YAHOO.widget.DS_XAJAXConnector.prototype.doQuery = function(oCallbackFn, sQuery, oParent)
{
doQueryXajax(oCallbackFn, oParent, sQuery);</p><p>return;</p><p>}

DoQueryAjax is our JS function that wraps the xajax request and saves the callback function in a global variable. The whole code customed to our need now looks like this:

var m_fCallBackToAutocomplete;
var m_oParent;
var m_sQuery;</p><p>function doQueryXajax(fCallBack, oParent, sQuery)
{
m_fCallBackToAutocomplete = fCallBack;
m_oParent = oParent;
m_sQuery = sQuery;
xajax_findNodeName(sQuery);
}</p><p>function autocompleteCallback(sResponse)
{
if (sResponse.constructor != Array)
{
sResponse = new Array();
}
m_fCallBackToAutocomplete(m_sQuery, sResponse, m_oParent);
}</p><p>

As you can see, we store the callback function and call it later on when the xajax response comes back and calls our wrapper autocompleteCallback.

The YUI autocomplete now talks to our server over our common channel xajax. We have our custom functions and spend just little time on coding and keep our way of handling ajax communication. If you have any questions, email me (about page) or write a comment.