Sunday, May 2, 2010

Which javascript library to use? JQuery UI or Dojo?

JQuery is great. I love it. But when it comes down to developing rich user interfaces dojo works better. It got many easy to use richer components which work in all browsers. JQuery Ui is good for light weight user interface work. There are lots of plug-ins available but it is hard to to get them work together in all browsers.

How to render topology/hierarchical diagram view using javascript

If you want to create a topology, flow chart, tree chart or organization chart like diagram in a web application, the following is a great JavaScript component  http://www.codeproject.com/KB/scripting/graphic_javascript_tree.aspx?

The component comes with lots of good examples and nice documents. This code is a bit old but it easy to fix it and make updates for your need. This comes with CPOL. I had to make minor changes to make it work in IE, Firefox and Safari.

Monday, April 19, 2010

jQuery UI: Adding close option to dynamically added tabs

If you want to add tabs dynamically and load data using AJAX. For example, when a node is double clicked in a tree, add a tab for that node and also don't add a duplicate tab. You could do some thing similar to the following:

Have a div with id "sdcMeterTabs". Inside this div, have a ul with id "sdcMeterTabHeader"


In java script:

Call the following when a tree node is double clicked:

function addTab(id, nodeType, nName ){

var idPrefix = nodeType+"_";// nodetype_
var nodeId = idPrefix+id;

//Finds all with an attribute id equals idPrefix+ id
if($("a[id="+idPrefix+ id +"]").length > 0) {
// tab is already loaded so find the href of the tab and load
var addedTab = $("a[id^="+nodeId +"]");
var addedTabHref = $(addedTab).attr("href");
$("#sdcMeterTabs").tabs('select', addedTabHref);
} else {
// tab not loaded so add a new one
$("#sdcMeterTabs").tabs("option", "nodeId",nodeId); // adding nodeId in option
$("#sdcMeterTabs").tabs({ ajaxOptions: { cache: false } });//cache fase for IE
$("#sdcMeterTabs").tabs("add", "metering/grid/newtab.jsf", nName); // Use URL for your tab content

}

return false;
}

Call the following from $(document).ready(function ()

function initializeTabs(){

//tabs init with a custom tab template and an "add" callback filling in the content
var $tabs = $('#sdcMeterTabs').tabs({
ajaxOptions: {
error: function(xhr, status, index, anchor) {
$(anchor.hash).html("Couldn't load this tab.");
}
,spinner: 'Loading...'
,cache: false
}
,cache: false
,idPrefix:""
,event: 'mousedown'
,tabTemplate: '


  • #{label} Remove Tab


  • '

    ,add: function(event, ui) {
    //hack to set id on dom so that later, tabs can be manipulated. Specially, want to stop duplicate tab addition
    var nodeId = $(this).tabs("option", "nodeId");
    ui.tab.id= nodeId;
    //select newely opened tab
    $(this).tabs('select',ui.index);
    return false;
    }
    ,select: function(event, ui) {
    $.get("dynamic/meteringBean/setMeterData.jsf?nodeTypeId="+ui.tab.id); //If you want to set some data

    }


    });

    If you want to add Close, Close Other and Close All menu/actions:

    function closeAllTabs(){
    var tabElement = $('#sdcMeterTabs');
    for (var i = tabElement.tabs('length') - 1; i >= 0; i--) {
    tabElement.tabs('remove', i);
    }
    return false;
    }

    function closeCurrentTab(){
    var tabElement = $('#sdcMeterTabs');
    var selected =tabElement.tabs('option', 'selected'); // => 0
    if(selected >= 0) {
    tabElement.tabs('remove', selected);
    }
    return false;
    }

    function closeOtherTabs(){
    var tabElement = $('#sdcMeterTabs');

    var selected = tabElement.tabs('option', 'selected'); // => 0
    if(selected >= 0) {
    // remove tabs after the selected tab
    for (var i = $('#sdcMeterTabs').tabs('length') - 1; i >= 0; i--) {
    if(i != selected) {
    tabElement.tabs('remove', i);
    }
    }
    }
    return false;
    }


    Sunday, November 9, 2008

    Hpricot for XML parsing


    Hpricot is flexible and fast html/xml parser.
    To install:
    $ sudo gem install hpricot

    require 'hpricot'

    doc = Hpricot(xml_string)
    doc.search("/rows/row").each do |row|
    group_and_row_id = row.get_attribute("id")
    row.search("/cell").each do |cell|
    cell_text = cell.html
    end
    end

    Caution with String concat <<

    If you have a string a = "test" and you want to append dynamic data, be careful with <If the dynamic data happens to be a Fixnum between 0 and 255, it is converted to a character before concatenation.

    >> a = "test"
    => "test"
    >> a << 12
    => "test\f"
    >>

    dhtmlxGrid in a Rails App

    Here is my experience using dhtmlxGrid in ROR app.
    In general, it is a nice component and comes with lots of sample code. Their support and knowledge is good. Their document list json as supported data type but json support is a subset of XML type. Even if you load the grind in JSON, when you serialize the grid, you get xml back and cell id is not sent back. In rails project, to generate xml, when you use xml builder, make sure, you don't indent. Grid dosn't load xml string with indentation.
    Use Builder::XmlMarkup.new (no indent)

    Don't use column name type

    I had a table and there was a business need to add a type column in the table. Adding a type column as an integer or as an string didn't work.
    "type" is a reserved field name for single table inheritance (STI). Active Record allows inheritance by storing the name of the class in a column that by default is called “type”

    Here are the magic field names in Rails:
    http://wiki.rubyonrails.com/rails/pages/MagicFieldNames

    HowtoGenerateJSON

    Recently, I needed JSON format to load data in dhtml component. It was trivial. I installed ruby-json gem. In my model class, I had to do the following:

    require 'json/objects'

    def to_json
    result = Hash.new
    rows = []
    qg_rows = all_records

    qg_rows.each_with_index do |row_data|
    row = {}
    data = []
    row_data.each {|cells|
    ------
    data << cell_data
    }
    row [:data] = data
    rows << row
    end
    result[:rows] = rows
    result.to_json

    end

    Saturday, October 11, 2008

    escaping javascript

    Javascript provides
    escape(string)
    method but it does not escapes the following chars:
    * @ - _ + . /

    Use encodeURIComponent(URIstring)instead. The encodeURIComponent method encodes all characters.
    Use the decodeURIComponent() function to decode URIs encoded with encodeURIComponent().

    Friday, October 10, 2008

    URI escaping

    Which one to use? URI.escape or CGI.escape? Lets look at irb:
    >> URI.escape('Test <>?/&;=:.')
    => "Test%20%3C%3E?/&;=:."
    >> CGI.escape('Test <>?/&;=:.')
    => "Test+%3C%3E%3F%2F%26%3B%3D%3A."

    CGI.escape is looking better but it does not escape dots which gives "RoutingError No route matches". Here is the solution working for me
    >> test = URI.escape(CGI.escape('Test <>?/&;=:.'),'.')
    => "Test+%3C%3E%3F%2F%26%3B%3D%3A%2E"
    >> CGI.unescape(test)
    => "Test <>?/&;=:."

    Sunday, October 5, 2008

    Formatting phone numbers, Fax numbers

    I created a plug-in act_as_formatted to format numbers, phone/fax numbers and zip codes. If there is number field (stored as integer in DB) and user formats it using "," for example 10,010, this gets saved as 10 (without special handling). To solve this problem I created a plug-in act_as_formatted. This plug-in creates *_ui and *_ui= methods on ActiveRecord models. attrubute_ui does the formatting and attribute_ui= ensures, right data is being saved. Example usage:
    In model
    acts_as_number :revenue
    acts_phone_number :phone
    In view use attribute_ui
    <%= company.revenue_ui %>
    <%= company.phone_ui %>
    ---------------------------------------
    Here is the code. Ideally model should not be using ActionView::Helpers::NumberHelper but for now this seems to be working fine for e.

    class ActiveRecord::Base
    include ActionView::Helpers::NumberHelper
    include ERB::Util

    def self.format_as_number(*args)

    args.each do |arg|

    define_method "#{arg.to_s}_ui=" do |number_string|
    if number_string
    # If a valid number, sanitize to digits only
    if valid_number? number_string
    number_string.gsub!(/[^0-9]/, "")
    end
    end
    write_attribute(arg, number_string)
    end
    end

    args.each do |arg|
    define_method "#{arg.to_s}_ui" do
    val = read_attribute(arg)
    begin
    number_with_delimiter val if val
    rescue
    val
    end
    end
    end

    define_method 'validate' do
    args.each { |arg|
    error_message = 'is invalid. It can contain only digits'
    if !eval(arg.to_s).nil?
    errors.add(arg, error_message) unless valid_number? eval(arg.to_s)
    end
    }
    end
    end

    def self.format_as_phone_number(*args)

    args.each do |arg|

    define_method "#{arg.to_s}=" do |number_string|
    if number_string
    # If a valid phone number(can contain only 0-9/-()+xX),
    # sanitize it to "digits only" only if 10 digits long
    # If number has more that 10 digits, store it as is
    if valid_phone? number_string
    numbers = number_string.gsub(/[^0-9]/, "")
    if numbers.size == 10
    number_string = numbers
    end
    end
    end
    write_attribute(arg, number_string)
    end
    end

    args.each do |arg|
    define_method "#{arg.to_s}_ui" do
    val = read_attribute(arg)
    begin
    if val
    numbers = val.gsub(/[^0-9]/, "")
    #Format 10 digits phone number only
    if numbers.size == 10
    return number_to_phone(numbers,:area_code => true)
    end
    end
    val
    rescue
    val
    end
    end
    end

    define_method 'validate' do
    args.each { |arg|
    error_message = 'is invalid. It must contain at least 5 digits, only the following characters are allowed: 0-9/-()+x'
    if !eval(arg.to_s).nil?
    errors.add(arg, error_message) unless valid_phone? eval(arg.to_s)
    end
    }
    end
    end

    # Valid phone number can contain only 0-9/-()+xX and whitespace.
    def valid_phone?(number)
    return true if( number.nil? || number.strip.size == 0)
    n_digits = number.scan(/[0-9]/).size
    valid_chars = (number =~ /^[xX.+\/\-() 0-9]+$/)
    return n_digits >= 5 && valid_chars
    end

    # Valid number can contain only digits and ,.
    def valid_number?(number)
    return true if( number.nil? || number.strip.size == 0)
    valid_chars = (number =~ /^[\,\ 0-9]+$/)
    return valid_chars
    end

    end

    Saturday, October 4, 2008

    Protecting app from Cross Site Scripting

    RoR provides methods for escaping metacharacters. To HTML escape data in RoR simply add an "h" inside your output tag.

    It is hard to remember using this everywhere. 

    There are several plug-in available which you can install and forget about adding "h". I tried auto_escape plug-in. This modifies ActiveRecord and and automatically applies CGI.escapeHTML to all text column. The plug-in works by defining an after_find call_back from ActiveRecord. Whenever a record is found and loaded from DB, this escapes text columns. Plug-in worked great but it slowed down the APP.

    xss_terminate plug-in makes stripping and sanitizing HTML automatic. Install the plug-in and forget about h() because you don't need to. This plug-in makes use of before_save hook and strips HTML tags. Generally there are more read operations than save, so performance wise this plug-in worked better for my case.

    Tuesday, July 15, 2008

    Textfield with auto complete

    I wanted to add auto complete feature in a form field. I used auto_complete plug-in. It is a great plug-in, very simple to use. In few lines of code, I was able to get this up and running.

    1) To install run script/plugin install auto_complete
    2) In controller, add auto_complete_for :tag, :name where Tag is a model and name is a field, I wanted my auto complete on.
    3) In view add <%= text_field_with_auto_complete :tag, :name %>. The method it implements will search through all the Tags in you records database and do a LIKE comparison on the name column, comparing the name to the contents of the tag[title] form field.
    4) I had to reopen auto_plug to fix "Mysql::Error: #23000Column 'name' in where clause is ambiguous". In my case, for auto_complete_for method I was passing :joins condition.
    I wanted to show tags for a given model. Both Tag and Model had same column name "name". To fix the problem, I created another plug_in name auto_complete_extended with single class init.rb. In init.rb I reopened auto_complete_for method (as follows):

    class ActionController::Base
    # Reopening auto_complete_for from auto_complete plug-in
    ### This is copy of auto_complete_for except for the part in condition where it qualifies column name with table name.

    def self.auto_complete_for(object, method, options = {})
    define_method("auto_complete_for_#{object}_#{method}") do
    find_options = {
    :conditions => [ "LOWER(#{object.to_s.pluralize}.#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ],
    :order => "#{method} ASC",
    :limit => 10 }.merge!(options)

    @items = object.to_s.camelize.constantize.find(:all, find_options)

    render :inline => "<%= auto_complete_result @items, '#{method}' %>"
    end
    end
    end

    Thanks to for the following post. It helped me get started. http://codeintensity.blogspot.com/2008/02/auto-complete-text-fields-in-rails-2.html

    Monday, June 23, 2008

    File upload with attachment_fu

    Attachment_fu is a great plug-in to upload a file. I used Mike Clark's blog as an example. By default, it gives the following validation message:
    Content type can’t be blank
    Content type is not included in the list
    Size can’t be blank
    Size is not included in the list
    Filename can’t be blank

    To get better validation message, either reopen attachment_fu.rb or do your own validation in your model. The following blog explains it nicely.

    If you are using MySQL, you may want to increase max_allowed_packet size in “my.cnf”. The default size is apparently 1M. If you don't change this and upload a file larger than 1 M, you will get “ActiveRecord::StatementInvalid: Mysql::Error: #08S01Got a packet bigger than ‘max_allowed_packet’ bytes: INSERT INTO...

    Sunday, June 22, 2008

    WYSIWYG-editor on Rails

    I found the following post very helping http://public.ok2life.com/welcome/index/48 and http://public.ok2life.com/welcome/index/49.html and http://www.fckeditor.net/demo. We are using FCKEditor and so far it is going very smoothly. In order to add a custom toolbar option, you need to write a plugin to fckeditor. In order to make spell checker to work, you need to install aspell http://wiki.lyx.org/Mac/MacSpelling and include file ActionView::Helpers::SanitizeHelper. I added the following line application's initializers dir
    include ActionView::Helpers::SanitizeHelper
    This was needed in Fckeditor: Version 0.4.3.

    To write a plug-in to fckeditor, follow direction on http://docs.fckeditor.net/FCKeditor_2.x/Developers_Guide/Customization/Plug-ins. After reading documentation from this link, I used the following plugin as an example (note: fckeditor comes with this plug-in): /fckeditor/public/javascripts/fckeditor/editor/plugins/placeholder/

    Graph/Charts

    For charts on rails, I looked GRUFF and OpenFlash. Open flash is very easy to use and provides more configuration options. You can get more information on open_flash_chart plug-in from the following http://teethgrinder.co.uk/open-flash-chart . I had to reopen Graph from open_flash_chart library, and redefine some methods to make look and feel changes.
    Sample:

    #Redefining method pie(alpha, line_color, style, gradient = true, border_size = false)
    # Original method had bug where it won't let you drop gradient
    def pie(alpha, line_color, style, gradient = true, border_size = false)
    @pie = "#{alpha},#{line_color},#{style}"
    if !gradient
    #@pie += ",#{!gradient}" # This is bug and won't drop gradient color
    @pie += ",#{gradient}"
    end
    if border_size
    @pie += "," if gradient === false
    @pie += ",#{border_size}"
    end
    end

    # Original Graph class is not exposing grid color setters.
    def set_grid_color( color = '')
    @x_grid_color = color
    @y_axis_color = color
    @x_axis_color = color
    @x_grid_color = color
    @y_grid_color = color
    end


    Ruby/Rails IDE

    TextMate is the editor of choice for most folks on Mac but I am from Java and Windows. I missed rich IDE like features in TextMate. Most of all I missed nice debugging support. I evaluated Netbeans(6.1 RC2) and Aptana Studio(Eclipse based: build: 1.1.6.009905). I liked Aptana. Netbeans had very basic debugging support. Aptana debugging is richer. I tried using TextMate for a week too. In my opinion, if you are comfortable with Eclipse IDE, and if you use Aptana, you are not missing any thing big from TextMate. Here are more resources:
    http://aptana.com/rails
    http://tnlessone.wordpress.com/2007/02/28/ruby-rails-ide-comparison-idea-netbeans-radrails/

    Books needed to get you started

    Ruby for Rails: Ruby Techniques for Rails Developers by David Black is the best beginner book for learning ruby. This book gives you very good base on Ruby. I tried reading Programming Ruby: The Pragmatic Programmer's Guide as 1st book but it didn't go that well. After reading Ruby for Rails, I read Agile Web Development with Rails, 3rd Edition. This will get you started with Ruby and Rails. The Ruby Way and The Rails Way are good reference books .