Sunday, September 2, 2012

Draggable Lightbox via HTML5

There is something with HTML lightboxes / modals that annoys me: they are usually not draggable.

The usual way to go is using draggable from jqueryUI . However I tend to flee from jqUI based solutions due to the code bloat it means (they are bullet-proof but they are heavy). Other solutions are more appealing. However I realized that almost all browsers provide support for 'native' drag and drop: why not to use it for my purpose?

The solution

The solution takes only ~20 lines of javascript (it controls that the modal is not moved out of bounds).
Suppose that your lightbox has a wrapper (the window that displays content) and an overlay. You have to bind the 'dragstart' and 'dragend' events to the wrapper (and do it only ONCE, for example after it is created). For the overlay, we bind the events 'drop' and 'dragover' (again, do it only once)

var opt = {};
$wrapper.bind('dragstart', opt,  function(ev){
    var opt = ev.data, 

    // we store the difference between the element top / left, and the mouse cursor, so that we can apply them once the object is drop
    $el = $(this), off =  $el.offset();
    opt.offX = off.left - ev.screenX;
    opt.offY = off.top - ev.screenY;

    var e = ev.originalEvent;
    e.dataTransfer.effectAllowed = e.dataTransfer.dropEffect = 'move';
    e.dataTransfer.setData('text', this.id);//we need to set some data
    $el.css({
        opacity:0.4
    });

}).bind('dragend', opt, function(ev){
    var opt = ev.data,
    //only if new top AND new left makes the box stay within the window
    $el = $(this), newX = ev.screenX + opt.offX, newY = ev.screenY + opt.offY, $win = $(window);
    newX = (newX < 0) ? 0 : Math.min(newX , $win.scrollLeft() + $win.width() - $el.outerWidth() );
    newY = (newY < 0) ? 0 : Math.min(newY , $win.scrollTop() + $win.height() - $el.outerHeight() );

    $el.css({
        top: newY,
        left: newX,
        opacity: 1
    });
});


$overlay
.bind('drop', function(ev){
    ev.stopPropagation(); // Stops some browsers from redirecting.
    ev.preventDefault();
    return false;       
}).bind('dragover',function(ev){
    if (ev.originalEvent.dataTransfer.getData("text")){ //allow to drop only our things
        ev.preventDefault(); //informs that we can drag here
        return false;
    }
});


Simple and straightforward!

Note: for a JS only implementation, simple and effective, take a look at http://css-tricks.com/snippets/jquery/draggable-without-jquery-ui/ 

Friday, August 31, 2012

Git :: Show a file from other branch


Sometimes yo need to view the contents of a file in other branch, without doing a checkout. This is super easy with git:
git show branch:file
 
Where branch can be any ref (branch, tag, HEAD, ...) and file is the full path of the file.
To store the file for a later read:

git show branch:file > exported_file 
 
 
(via http://stackoverflow.com/a/7856446/1265056 ) 

Monday, July 30, 2012

Using Prawn and Rails to email a pdf

In MicroHealth we needed to email a pdf for some users.
The document that is attached can be downloaded as well, and I wanted to reuse as much code as possible.

For the views we use prawn and prawnto gems. The recipe consists of 3 steps:
- generate and save the pdf to disk
- email (attach the pdf)
- delete the pdf

The 'view' used to generate the pdf document is located in /app/views/my_controller/show.pdf.prawn and is shared for both emailing and downloading actions.

Generate a pdf file AND save it to disk

The key is to inherit from Prawn::Document, and to provide the @ instance variables that the view expects:

 class MyGenerator  < Prawn::Document
 

  def initialize(options)
    options && options.merge!({:inline=>true})
    create_instance_variables(options.delete(:variables))
    super(options)
  end

  def render_template(template)
    pdf = self
    pdf.instance_eval do
      eval(template) #this evaluates the template with your variables
    end
    ensure_path
    pdf.render_file(File.join(output_path,filename))
  end

  private

  def create_instance_variables(vars)
    return if vars.blank?
    vars.each_pair do |k,v|
        instance_variable_set("@#{k}", v)
    end
  end

  def output_path
    @output_path ||= File.join(Rails.root,'tmp','documents')
  end

  def ensure_path
    FileUtils.mkdir_p(output_path)
  end

  def filename
    @output_file ||= "#{Process.pid}::#{Thread.current.object_id}.pdf"
  end

end



For example my view expects some variables as @start, @end and @records, and that the pdf document variable is named 'pdf'

template = File.read("#{Rails.root}/app/views/my_controller/show.pdf.prawn")
writter = HemoPdfReport.new(:page_size => 'A4', :page_layout  => :landscape, :variables => {:start => params[:start], :end => params[:end], :records => @results})
attachment = writter.render_template(template)
begin
#send
  MyPdfMailer.attachment_email(
   :user => @user,
   :destination => @email,
   :message => @text,
   :attachment => attachment.path).deliver
              @report.save!
ensure
   FileUtils.rm_f(attachment.path)

end

Things to note:
  • The create_instance_variables method copies the 'variables'  parameter used in the initialization, to instance variables.
  • As our generator is a PrawnDocument, we can pass it as the 'pdf' variable that the view expects (note the line pdf = self before the eval)
  • We automatically provide a filename to the output. We could have used a timestamp but we use one based on the current thread. The key here is to avoid using an static name (we can have several processes / threads generating documents concurrently)

Emailing PDF

Emailing a file is really simple, just follow the http://guides.rubyonrails.org/action_mailer_basics.html
 
An example of my mailer
  def attachment_email(options)
    attachment = options[:attachment]
    @user = options[:user]
    @destination = options[:destination]
    @text = options[:message]
    attachment.present? && attachments['report.pdf'] = {
      :mime_type => 'application/pdf',
      :content => File.read(attachment)
    }
    subject = "Your Pdf Document"
    mail(:to => @destination, :subject => subject)
  end


Delete file

Once the file is emailed, dont forget to delete it. The ensure block is meant for this.

Profit!

Monday, June 18, 2012

Deploy with capistrano an specific branch


To deploy with capistrano a different branch than the specified in the configuration (ex: master/HEAD) just use:
cap staging deploy -s branch=my_branch
It deploys my_branch into staging environment. If you are not using the multistage configuration see , it becomes
cap staging deploy -s branch=my_branch

If capistrano does not obey, check that in your deploy files (for each environment) you are getting the branch variable from the environment:

#in  config/deploy.rb and each for config/deploy/*
set :branch, fetch(:branch, "master")
 

Profit!

Monday, May 7, 2012

Rails: Using no id keys in a has-many-belongs-to association

This small recipe is useful for when you have to link 2 models with no id keys. Our case is for users that can send and receive invitations. The key to make the lookup for the 'invited' users is the email, not an id. Since the 'invited' users could have not completed the registration process, we could not use a 'recipient_id' column. Note that the column used for the lookup takes a different name on both models.
Once built, the relationships are really easy:

class User < ActiveRecord::Base
(...)
   has_many :invitations_sent,
      :foreign_key => 'user_id',
      :class_name => 'Invitation', :dependent => :destroy, :inverse_of => :user
   has_many :invitations_received,
      :primary_key => 'email', :foreign_key => 'recipient_email',
      :class_name => 'Invitation', :inverse_of => :recipient
(...)
end




class Invitation < ActiveRecord::Base
(...)
   belongs_to :user, :inverse_of => :invitations_sent
   belongs_to :recipient,
      :primary_key => 'email', :foreign_key => 'recipient_email',
      :class_name => 'User', :inverse_of => :invitations_received
(...)
end


Et voila!

Sunday, March 18, 2012

Restore sound in Ubuntu 11.10

Sometimes, in my Ubuntu 11.10, sound is not working after the system started.
It happens from time to time, and can be solved rebooting or restarting the session. However it is really annoying making a reboot once you have all your toolbox of programs running.
The solution is as simple as opening a Terminal (ctrl + alt + T)
and restating the pulse audio daemon:
pulseaudio -k  (no need to be root)
pulseaudio --start
pulseaudio --check

now you can use you media player with sound!

Monday, February 13, 2012

Request Log Analyzer

Casi por casualidad, y a tavés de la página Ruby Toolbox he descubierto una pequeña maravilla que te permita analizar tus ficheros .log de tu aplicación Rails: tiempos de accesos medios, maximos, controladores y acciones, horas del dia más solicitadas... y seguramente mucho mas.

Es la gema request-log-analyzer , y es compatible con Rails3 (por lo menos). Disponible en github . Alli tienes una wiki bastante extensa con su uso.

A disfrutarla.