Using native JavaScript objects from Opal

Question: can I call JS functions from Opal?
Answer: totally!

Opal standard lib (stdlib) includes a native module, let’s see how it works and wrap window:

require 'native'

window = Native(`window`) # equivalent to Native::Object.new(`window`)

Now what if we want to access one of its properties?

window[:location][:href]                         # => "http://dev.mikamai.com/"
window[:location][:href] = "http://mikamai.com/" # will bring you to mikamai.com

And what about methods?

window.alert('hey there!')

So let’s do something more interesting:

class << window
  # A cross-browser window close method (works in IE!)
  def close!
    %x{
      return (#@native.open('', '_self', '') && #@native.close()) ||
             (#@native.opener = null && #@native.close()) ||
             (#@native.opener = '' && #@native.close());
    }
  end

  # let's assign href directly
  def href= url
    self[:location][:href] = url
  end
end

That’s all for now, bye!

window.close!

jQuery injection for profit and profit, cause fun is overrated

Working on a project for an important client (more on that in the next months), we came up with the need to inject jQuery on a page, making it available to your functions.

In addition to that we wanted to be sure it didn’t conflict with other jQueries the page was already using, and, if a new version of jQuery was already available, use that instead of injecting a new one.

This whole injection thing is not very complicated per se. You just have to be sure you’re not messing up with what’s already in the page.

Let’s walk through our solution to see how we implemented it. All the code you see here will be CoffeeScript, the javascript translation is really straightforward and won’t be provided 🙂

We start with an empty html document, something like this:

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <script src="jquery-injection.js"></script>
</head>
<body>
</body>

jQuery-injection.js is the compiled version of our coffeescript source.

The simplest way to inject jQuery would be something like this:

@Injector = 
    injectjQuery: ->
        script = document.createElement('script')
        script.src = "//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"
        head = document.getElementsByTagName('head')[0]
        
        head.appendChild(script)

We’re creating a globally available Injector object with an injectjQuery function that creates a script element, sets its src attribute and adds it to head.

This is all fine and dandy, but what if we already have jQuery? This would load jQuery twice and there would be namespace conflicts. What we want to do is check for an existing jQuery and use jQuery.noconflict() to make sure they don’t clash.

checkAndInjectjQuery: ->
    if jQuery?
      if @versionCompare(jQuery.fn.jquery, "2.0.3") < 0
        @injectjQuery(true)
      else
        @jq = window.jQuery
    else
      @injectjQuery(false)   

What’s going on in here? We check if jQuery exists, and that its version is at least 2.0.3. The version check is done via versionCompare, a coffeescript simplified port of this code I found on StackOverflow.

If jQuery exists and is sufficiently new, we just map this.jq to window.jQuery. Our local jq variable is what we’re gonna use inside our functions instead of using $.

If you paid enough attention, you will have noticed we’re calling injectjQuery with a parameter.

  injectjQuery: (saveOriginal) ->
    script = document.createElement('script')
    script.src = "http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"
    head = document.getElementsByTagName('head')[0]

    if saveOriginal
      @jq = $.noConflict(true)

    head.appendChild(script)

injectjQuery(true) will assume that there’s another jQuery in the page, so it saves it (temporarily) in this.jq, and then cleans after it, making sure it won’t be there when the new jQuery loads.

Wait, did I say “when the new jquery loads”? You can bet I did! Everything we’re doing is asyncronous, so we can’t be sure that the new jQuery is already available.

Avoiding potential issues with that requires some callback magicks. First we edit injectjQuery, adding, immediately after the last line, the following code

@checkLoaded(saveOriginal)

checkLoaded is a function that keeps calling itself until jQuery is effectively loaded.

 checkLoaded: (saveOriginal) ->
    window.setTimeout( =>
      if !jQuery?
        @checkLoaded(saveOriginal)
      else
        if saveOriginal
          temp = @jq
          @jq = $.noConflict(true)
          window.jQuery = temp
          window.$ = jQuery
        else
          @jq = jQuery

        @continueInitialization()
    , 500)

It recognizes the same saveOriginal parameter we used before, restoring the original jQuery where it belongs, and leaving us with our new jQuery, available through this.jq.

continueInitializazion is the function that should handle all of your post-jquery-loaded needs.

In this gist you’ll find the complete Injector class, with some bonus console.log calls to better understand what’s going on.

Thank you for reading up to this last paragraph, and have fun injecting jQuery where you please.

Using HTML5 date fields mantaining backward compatibility

Chrome and Opera (respectively from versions 20 and 12) implement a native date picker. Now, when you have an input field with the date type:

<input type="date" name="user[born_on]" id="user_born_on" />

these two browsers will automatically inject in the page their native datepicker, according to the input[type=date] HTML5 specs.

Native datepicker

And, at least in Chrome, the widget is really really good.

Btw, this doesn’t happen only for date fields. In fact in HTML5 we can find other new input types specifically created to deal with dates and times: datetime, datetime-local, time. Chrome and Opera will show a picker also for these input types.

There is only one little, negligible problem: as I said this feature is implemented only in Chrome and Opera!
Continue reading “Using HTML5 date fields mantaining backward compatibility”

Avoiding Safari exit confirmation dialog for dirty forms

Safari asks close confirmation if a form field isn't clean

Safari asks for your confirmation if you accidentally try to close a page if you filled a form but never submitted. This saved my day more than once and it’s undoubtedly helpful.

The problem is that if you’re sending the form via ajax Safari still thinks you’re loosing some data. Luckily the fix is easy:

var postBody = document.querySelector('textarea.post-body')
postBody.defaultValue = postBody.value

Opal: give it a try

If you are a Ruby developer, you’re probably constantly complaining about Javascript’s quirks 🙂

Yes, Coffeescript is a great abstraction. It gives you a nice syntax that is both easier to read and easier to write. Stuff like the [fat arrow](Fat Arrow explaination) and the class keyword are undoubtfully better than their vanilla JavaScript counterparts. In addition Coffeescript compiles to plain Javascript without the need of any framework or third party library. In a nutshell, Coffeescript is the perfect tool when you need to write all that javascript glue to work with jquery plugins, backbone and all that stuff that we know well.

But when you need to write your own framework or to write a more complex app, even Coffeescript could not be the right tool.
Continue reading “Opal: give it a try”

“Use Node.js FOR SPEED” — @RoflscaleTips

I obeyed, therefore I wrote an opal NPM package.

Now I can trash Rails and opal-rails and start working on Node only!

server = Server.new 3000
server.start do
  [200, {'Content-Type' => 'text/plain'}, ["Hello World!n"]]
end

For it I had to write the Server class:

class Server
  def initialize port
    @http = `require('http')`
    @port = port
  end

  def start &block
    %x{
      this.http.createServer(function(req, res) {
        var rackResponse = (block.call(block._s, req, res));
        res.end(rackResponse[2].join(' '));
      }).listen(this.port);
    }
  end
end

Put them in app.opal and then start the server:

$ npm install -g opal      # <<< install the NPM package first!
$ opal-node app

The running app

YAY! now I can roflSCALE all of my RAils apps!!1

Cross posted from Eliæ

Stop writing JavaScript and get back to Ruby!

Cross posted from: Eliæ

Remeber that feeling that you had after having tried CoffeeScript getting back to a project with plain JavaScript and felt so constricted?

Well, after re-writing a couple of coffee classes with OpalRuby I felt exactly that way, and with 14.9KB of footprint (min+gz, jQuery is 32KB) is a complete no brainer.

So let’s gobble up our first chunk of code, that will salute from the browser console:

puts 'hi buddies!'

You wonder how that gets translated?

Fear that it will look like this?

'egin',cb='bootstrap',u='clear.cache.gif',z='content',bc='end',lb='gecko',mb='g
cko1_8',yb='gwt.hybrid',xb='gwt/standard/standard.css',E='gwt:onLoadErrorFn',B=
'gwt:onPropertyErrorFn',y='gwt:property',Db='head',qb='hosted.html?hello',Cb='h
ref',kb='ie6',ab='iframe',t='img',bb="javascript:''",zb='link',pb='loadExternal
Refs',v='meta',eb='moduleRequested',ac='moduleStartup',jb='msie',w='name',gb='o
pera',db='position:absolute;width:0;height:0;border:none',Ab='rel',ib='safari',
rb='selectingPermutation',x='startup',m='hello',Bb='stylesheet',ob='unknown',fb
='user.agent',hb='webkit';var fc=window,k=document,ec=fc.__gwtStatsEvent?functi
on(a){return fc.__gwtStatsEvent(a)}:null,zc,pc,kc,jc=l,sc={},Cc=[],yc=[],ic=[],
vc,xc;ec&&ec({moduleName:m,subSystem:x,evtGroup:cb,millis:(new Date()).getTime(
),type:nb});if(!fc.__gwt_stylesLoaded){fc.__gwt_stylesLoaded={}}if(!fc.__gwt_sc
riptsLoaded){fc.__gwt_scriptsLoaded={}}function oc(){var b=false;try{b=fc.exter

Nope! it’s Chuck Testa

(function() {
  return this.$puts("hi buddies!")
}).call(Opal.top);

But it will be obviously a mess to integrate it with existing javascript!!1

Let’s how to add #next and #prev to the Element class:

class Element
  def next(selector = '')
    element = nil
    `element = jQuery(this.el).next(selector)[0] || nil;`
    Element.new element if element
  end
end

But I’m on Rails!

Here you served:

gem 'opal-rails'

Which il provide you a requirable libraries for application.js

//= require opal
//= require rquery

and the .opal extension for your files

# app/assets/javascripts/hi-world.js.opal

puts "G'day world!"

A template handler:

# app/views/posts/show.js.opal.erb

Element.id('<%= dom_id @post %>').show

and a Haml filter!

-# app/views/posts/show.html.haml

%article= post.body

%a#show-comments Display Comments!

.comments(style="display:none;")
  - post.comments.each do |comment|
    .comment= comment.body

:opal
  Document.ready? do
    Element.id('show-comments').on :click do
      Element.find('.comments').first.show
      false # aka preventDefault
    end
  end