Saturday, August 8, 2009

Tips on writing browser compatible css

Writing browser compatible css is a difficult job for people with less experience in css. Specially Internet Explorer make things interesting(or gives you headache, what ever you prefer ;) ). As an application grows it becomes more and more difficult to manage browser compatibility. And as Internet explorer continues to release new versions, it leaves a few time for developers to spare. Some times different browser is needed to tweak differently. It may be the size of the font, padding floating problems etc.

People then started to use Conditional Comments like bellow to give browser compatibility,

<!--[if lte IE 6]>
<link rel="stylesheet" type="text/css" href="ie_hacks.css" />
<![endif]—>

And there is the !important hack

Normally in CSS whichever rule is specified last takes precedence. However if you use !important after a command then this CSS command will take precedence regardless of what appears after it. This is true for all browsers except IE. Here you can find more.

h1 { font-size: 1.6em !important; font-size: 1.4em; }

Here IE still takes 1.4em as the font size.

Tantek box model hack

Tantek introduced a hack for ie and opera regarding the box model problem.

Hybrid Hacking defined css as,

#wrapper {
width: 770px;
wid\th: 750px;
}

It's a legal 'escape' backslash and should be ignored, but when it is inside the property name it is not ignored by IE5 and IE5.5 for Windows. Instead it causes those browsers to ignore the following "t" character in this case, thus making the property name unreadable to them. Which was explained here.

Then there was the The Child and Adjacent Sibling Hacks, as IE does not understands html>body. Which looks like,

#wrapper {
min-height: 500px; /* IEwin does not support min-height */
height: 500px;
}

html>body #wrapper {
height: auto;
}

This is discussed more elaborately here.

Here is a list of rule compatibility of browsers which is really useful.

An easy solution to write css which can easily support different browser

It is a combination of Javascript and CSS. Here is the Javascript that detects the browser and add a dummy class to body tag.

if ( $.browser.msie && $.browser.version == "6.0" ) {
$("body").addClass("ie6");
}
Although it is written in JQuery, there are plain Javascript available.

So now if your clients browser is IE6 then you already have a dummy class added to you body tag. Now you can write IE6 specific hack easily.

h1 { font-size: 1.6em; }
.ie6 h1 { font-size: 1.4em; }

It is more readable and you can provide different hack for different browser and versions with this hack.

References

http://www.positioniseverything.net/articles/ie7-dehacker.html

http://www.evolt.org/ten-css-tricks-you-may-not-know

Thursday, August 6, 2009

Using acts_as_auditable for keeping history for model activities in rails

Keeping change history for important models are crucial for some application. And acts_as_auditable provides a clean implementation for keeping history.

Install

1. Download acts_as_auditable and put in your vendor folder

2. run

ruby script\generate audit

from your application directory. Which will generate a migration script to create audits table and a model named Audit.

Expectation

1. User model that inherits from ActiveRecord::Base with an instance method of auditor_name(it is required as it stores the auditors name as well as the auditor’s reference for keeping history).

2. A property named auditor(which will be injected in the model where you are using audit) should be prefilled with the user object who is changing the model.

3. Now you can use audit in your model as,

audit :when => :before_update,
      :if => lambda {|work_item| work_item.changed? },
      :with_message => lambda { |post| 'Something changed :)' }

Here

:when         => any active record callbacks (like :before_create)
:if           => provide a condition which will be checked to create the audit
:with_message => the message to be stored

Well that’s about it to use acts_as_auditable.

Implementing in real project

The challenge that I faced is to create a human readable message for model and make it maintainable.

I actually used audit like,

audit :when => :before_update,
      :if => lambda {|work_item| work_item.changed? },
      :with_message => lambda { |work_item| work_item.audit_message }

Now here is the code to generate the message.

def audit_message
  history_text = ''
  self.changes.each do |field, change|
    history_text += "#{field.humanize} changed from '#{change[0]}' to '#{change[1]}'\n" unless field.ends_with?('_id')
  end

  history_text += audit_message_for('project_id', Project, :name, 'project')
  history_text += audit_message_for('sprint_id', Sprint, :name, 'sprint')
  history_text += audit_message_for('responsible_person_id', User, :full_name, 'responsible_person', 'Responsible Person')
  history_text += audit_message_for('release_id', Release, :name, 'release')
  history_text += audit_message_for('point_id', Point, :name, 'point')

  return history_text
end

And here is the nasty part that at least cleans the audit message implementation. It helps to generate audit message for reference objects.

private
# Used for making the audit text readable
#
# ==== Parameters
#
# * +field+ - The database field to inspect changes
# * +klass+ - The class for the referenced object
# * +klass_property+ - The property of the object which will be readable to store audit data
# * +readable_name+ - readable name for the class. By default it is klass.to_s
#
# ==== Returns
# The audit text for that field.
# If there is no change in the field then empty string is returned
def audit_message_for(field, klass, klass_property, belongs_to, readable_name = klass.to_s)
  history_text = ''
  seperator = '\n'
  #as this is done with reflaction, catching exeptions for protection
  begin
    if self.send("#{field}_changed?")
      field_was = "#{field}_was"

      changed_from = klass.find(self.send(field_was)).send(klass_property) unless self.send(field_was) == nil
      changed_to   = self.send(belongs_to).send(klass_property) unless self.send(field) == nil

      if self.send(field_was) == nil
        history_text = "#{readable_name} assigned to '#{changed_to}'#{seperator}"
      elsif self.send(field) == nil
        history_text = "Removed #{readable_name} from '#{changed_from}'#{seperator}"
      else
        history_text = "#{readable_name} changed from '#{changed_from}' to '#{changed_to}'#{seperator}"
      end
    end
  rescue Exception => ex
    logger.error "Error #{ex.to_s}"
  end

  return history_text
end

Another thing to consider is assigning the auditor before saving the history. It is cleaner if you define a method named auditor. That is how you can avoid assigning to that attribute.

def auditor
  return self.new_record? ? self.creator : self.updator
end

But for implementing like this you need to modify some code in the library. Open the vendor\plugins\acts_as_auditable\lib\shooter\acts\auditable.rb and remove the line,

attr_accessor :auditor

Otherwise your method will be overwritten.