Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interact/access javascript objects/methods #14

Closed
adambeynon opened this issue Jul 12, 2011 · 11 comments
Closed

Interact/access javascript objects/methods #14

adambeynon opened this issue Jul 12, 2011 · 11 comments
Labels

Comments

@adambeynon
Copy link
Contributor

Currently Opal needs to wrap any native js construct (arrays, jquery instances, custom libraries etc) so they can be accessed in a ruby way - i.e. so we still get method_missing, "getter" and "setter" methods, operator overloading etc. Opal still needs a proper way to interact with js objects without using the backtik approach.

One solution is to use the :: construct to access javascript properties. For instance, to get an array length using its native length property, we could use:

[1, 2, 3, 4]::length           # => compiles to [1, 2, 3, 4].length

Instead of the method call version:

[1, 2, 3, 4].length             # => compiles to [1, 2, 3, 4].m$length()

Another approach is to make it obvious that we are running directly on top of javascript, and maybe make an "opalscript" branch which is more of a ruby syntax for javascript, rather than a ruby runtime on top of javascript. Method calls would follow perhaps Coffeescript's style where a call without args and parans is simply a property access.

@k33g
Copy link

k33g commented Jul 25, 2011

Hi,

can you please give some samples about using jquery or dom (like document.querySelectorAll), how to pass parameters to js commands ?

it's very interesting to be able coding with ruby in navigator, thanks for this

sorry about my english (i'm french)

@adambeynon
Copy link
Contributor Author

Well, using your given example Im currently trying to solve two issues: accessing 'global' js objects/variables (e.g. document), and calling native functions on objects (e.g. .querySelectorAll).

Accessing global js objects

The opal compiler has no way to know about global objects at compile time, i.e. it cannot look through your code and determine that document is actually the global document variable. Rubys syntax says that if a variable is not defined in the current scope, then it is intact a method call (called on the receiver). Unless every identifier is compiled to check for this, we cannot know how to treat it.

One solution I'm looking at is to override the use of global variables to compile down to basic javascript variable names. For example, the following ruby code:

foo = $document;

Would compile into something like:

var foo = document;

The opal compiler will determine that global var access is just accessing global javascript variables. Obviously special ruby globals ($$, $:, etc) will compile into a wrapper to their respective ruby variables. Similarly, global assignment could (should?) generate a global javascript variable.

If we wanted to be clever, we could compile these globals to check for a variable existence first and return nil if the given global var doesn't exist - so our example above could become:

var foo = (typeof document != undefined ? document : nil);

This is the best way I can see for easy access to js variables while keeping (mostly) ruby semantics.

Calling native functions

Once we have a reference to these global variables, accessing their functions is the tricky part. A feature (or pain) of js is that you can access functions, or other properties, of an object by omitting call parens (unlike ruby which calls a method anyway).

To keep things in opal fast, strings, numbers and arrays point to their native javascript equivalents. This has a side effect that every ruby array, string and number have these js properties already attached. The opal compiler therefore appends m$ to every method name so that [1, 2, 3].length compiles into [1, 2, 3].m$length() which avoids naming clashes as much as possible.

This also means that accessing a function, like querySelectorAll is not so easy, as calling that method on our document variable will compile into document.m$querySelectorAll(), which is why I proposed the :: syntax for native function calls.

The alternative

The alternative to these two 'solutions' is to make use of the current back tick syntax that can hold raw javascript, and is just output as given. For example, to use your given method, you could do:

elements = `document.querySelectorAll('div')`

# pass elements somewhere else
do_something_with_elements elements

# raises an error as elements does not have any ruby methods..
elements.try_some_method()

As nice as being able to directly call js methods would be, loosing specific ruby features that make ruby great would remove the benefit of being able to use ruby in the browser. I hope this clears up the current method of accessing js objects and what might make it into opal next.

@k33g
Copy link

k33g commented Jul 29, 2011

wahooo! thanks a lot, i'm going to play with it

@k33g
Copy link

k33g commented Jul 29, 2011

OK, i've understood

 def change(txt)
    `document.querySelector('h1').innerHTML = txt;`
end

@al6x
Copy link

al6x commented Aug 5, 2011

Nice project, is it supposed to be compatible with Node.js?

About access to JS, maybe it's possible to use something like this?

[1, 2, 3, 4].native.length
[1, 2, 3, 4].js.length

@adambeynon
Copy link
Contributor Author

It does work with node.js, but not in the master branch. I will be pushing node.js support in a day or so once I tidy up the file system methods.

I do like the approach using native, or some other special keyword - there is temporary code in the propery_access branch for accessing global js variables using something similar, using a special opal keyword:

opal.document   # => document
opal.window       # => window

I'll keep you updated here on the node.js updates.

@meh
Copy link
Member

meh commented Aug 7, 2011

Why not using Ruby's global syntax to access globals?

$document and $window sounds better than opal.document and opal.window to me.

Didn't give a look at the property_access branch but I would go with defining if something is native or not in the class, with something on the lines of native :length, :size, :whatever and then check during compile if it's a native or a method call. Also adding Object#native, like @alexeyPetrushin suggested, would be cool.

@adambeynon
Copy link
Contributor Author

Yeah, the global syntax seems the better way. I think it should be used just for accessing globals - not writing, i.e. when setting a ruby global it shouldnt really set a global in js.

As for the property access, well, compile time detection wouldnt be all that easy. The compiler will not have access to some prototypes to check, for instance it won't know which jquery properties are simple properties, like length and selector, or which are actual functions. I guess you could have a couple of class methods such as:

class JQuery
    native_proptery :length, :selector

    native_function :next, :prev
end

... or something similar.

@meh
Copy link
Member

meh commented Aug 8, 2011

Agree with the global setter being Ruby only.

I'd sincerely go with the super syntax to discern properties from functions, as in, always need parenthesis to call a native function.

@adambeynon
Copy link
Contributor Author

Im not sure I follow.. how would the compiler determine that a method call on a object is trying to access native properties/functions compared to a normal ruby method call? The dynamism of ruby means theres no guaranteed way to know the type of the receiver and therefore what its native properties are.

@meh
Copy link
Member

meh commented Aug 8, 2011

Well, you coul keep a native thing table somewhere, but yeah, you're probably right, easier to go with native_property and native_function :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants