[![CircleCI](https://circleci.com/gh/effective-spa/jsrb.svg?style=svg)](https://circleci.com/gh/effective-spa/jsrb) [![codecov](https://codecov.io/gh/effective-spa/jsrb/branch/master/graph/badge.svg)](https://codecov.io/gh/effective-spa/jsrb) [![Maintainability](https://api.codeclimate.com/v1/badges/8bf4666ed7b983f01519/maintainability)](https://codeclimate.com/github/effective-spa/jsrb/maintainability) [![Gem Version](https://badge.fury.io/rb/jsrb.svg)](https://badge.fury.io/rb/jsrb) # Jsrb Jsrb is a template engine to generate JavaScript code in simple Ruby DSL. # Getting Started Jsrb handler works in `.jsrb` view files. All ruby syntax is available and `js` is provided in it. You can construct JavaScript code via `js`. ```ruby name = js.var!(:name) { 'foo' } ary = js.var! :ary obj = js.var! :obj result = js.var! # var name = 'foo'; # var ary; # var obj; # var _v1; // auto generated js.set! ary, [1, 2, 3] # ary = [1, 2, 3]; js.set! obj, { name: name, profile: { age: 20, sex: 'm' } } # obj = { # name: name, # profile: { # age: 20, # sex: 'm' # } # }; js.set! result, (obj.name + "(" + obj.profile.age + ")") # _v1 = obj.name + "(" + obj.profile.age + ")"; js.set! ary, ary.map { |x| x * 2 } # ary = ary.map(function(x) { # return x * 2; # }); js.if!(ary[1] === 4) { js.set! result, 'four' }.elsif(ary[1] === 2) { js.set! result, 'two' }.else { js.set! result, 'other' } # // The actual output will have certain immediate functions # // that preserve variable scope for each case. # if (ary[1] === 4) { # _v1 = 'four' # } else if (ary[1] === 2) { # _v1 = 'two' # } else { # _v1 = 'other' # } js.set! result, js.expr.Date.new # _v1 = new Date; js.set! js.expr.console.log('hello') # console.log('hello'); ``` # Usage In contrast to Ruby, statements and expressions are specifically distinguished as different elements in JavaScript. And the program is composed of a list of statements. This means that the jsrb file will have a series of statement pushing expression in it. To make clear whether a method is pushing statement or not, jsrb adopted the rule that the name of method pushing statement should be `#..!`. ## Statements ### Variable declaration `js.var!` pushes a **VariableDeclaration** into current context. ```ruby # with variable name and initializer js.var!('varname') { 100 } # var varname = 100; # without initializer js.var!('varname') # var varname; # variable name is auto-generated if not specified js.var! # var _v1; # var! returns Jsrb::ExprChain instance, so that you can # assign value with `.set!` method. a = js.var! js.set! a, 100 # var _v1; # v1 = 100; ``` ### If statement, and conditional expression `js.if!` pushes an **IfStatement** into current context, `js.if` to build **a conditional expression**. ```ruby # start with `#if!` # and chain `#elsif` to add next case. # Note that this is a statement, not expression. js.if!(v === 1) { # .. }.elsif(v === 2) { # .. }.else { # .. } # if you don't need else clause, close with `#end` js.if!(v === 1) { # .. }.end # if you want to regard this as an expression, use `#if` without exclamation. js.set! v, js.if(v === 1) { # .. }.else { # .. } ``` ### Expression statement `js.do!` pushes an **ExpressionStatement** of a given expression. ```ruby get_elements = js.expr[:getElements] js.do! get_elements.('.foo').forEach { |n| n.delete.() } # getElements('.foo').forEach(function(n) { return n.delete(); }); ``` ## Expressions Expression chain (`ExprChain` class) is an utility class to construct JavaScript expressions. ### Initialize with wrapping ruby values `js.expr` create a new `ExprChain` instance, taking an initial value optionally. Some methods in `js` automatically convert ruby value to ExprChain. ```ruby x = js.var! :x js.set! x, js.expr(100) # x = 100; # set! automatically wraps argument with ExprChain. js.set! x, 100 # x = 100; # # Note that if you need to compare a ruby value by operator with another one, # you have to wrap it. js.set! x, (js.expr(100) < js.expr.y) # (100 < js.expr.y) will fail. ``` See the conversion section to check mappings from ruby value to JavaScript one. ### Chains #### Member expression `ExprChain#[], #..` constructs **MemberExpression**. `#[]` and `#member!` is safe. `#..` can be used only if the name has no conflict. ```ruby x = js.var! :x obj = js.expr[:someObj] # js.expr with no argument constructs empty chain, # in which every member chain will be an identifier. js.set! x, obj.field # x = someObj['field']; js.set! x, obj[:field] # x = someObj['field']; js.set! x, obj.send # NOTE that this is interpreted as a ruby Object's method, and causes an error. ``` #### Assignment `ExprChain#set` constructs an **AssignmentExpression**. ```ruby a = js.var! :a # var a; js.do! a.set 100 # a = 100; # js.set!(a, b) is short hand of js.do!(a.set(b)) js.set! a, 100 # a = 100; ``` #### Function Call `ExprChain#call, so #.(), #.. with argument or block` constructs **CallExpression**. ```ruby console = js.expr[:console] # using call method js.do! console.log.('foo') # console.log('foo') js.do! console.log.call('foo') # console.log('foo') # using dynamic method # if #..() has at least one argument or block, it will be a call expression. js.do! console.log('foo') # console.log('foo') js.do! js.expr(:ary).forEach { |item| item.execute.() } # ary.forEach(function(item) { return item.execute(); }); ``` #### Operators Any ruby-overridable and JS-existing operators are overridden for chaining. Supported operators are: `** + - * / % >> << & ^ | <= < > >= == === != ! && ||`. ```ruby x = js.var! :x a = js.expr[:a] js.set! x, (a === 1) # x = a === 1; js.set! x, (a * a) # x = a * a; js.set! x, (3 * a) # raises an error because the method :* of Fixnum does not accept ExprChain as RHS. ``` #### New `ExprChain#new` constructs **NewExpression**. ```ruby x = js.var! :x js.set! x, js.expr[:Date].new # x = new Date; ``` #### Function expression `ExprChain#forall` constructs **FunctionExpression**. You can also construct it directly from Proc or passing a block. ```ruby ary = js.var! :ary, [1, 2, 3] js.do! ary.map((js.expr[:x] * 2).forall('x')) # ary.map(function(x) { return x * 2; }); js.do! ary.map { |x| x * 2 } # ary.map(function(x) { return x * 2; }); js.do! ary.map(->(x) { x * 2 }) # ary.map(function(x) { return x * 2; }); ``` # Conversion Every Ruby's generic value will be converted to Javascript value by following rule: |Ruby value|JavaScript| |---|---| | finite `100` | `100` | | NaN `Float::NAN` | `NaN` | | +Infinity `Float::INFINITY` | `Number.POSITIVE_INFINITY` | | -Infinity `Float::INFINITY` | `Number.NEGATIVE_INFINITY` | | `true` | `true` | | `false` | `false` | | `:foo` | `"foo"` | | `"foo"` | `"foo"` | | `nil` | `null` | | `[1, 2, 3]` | `[1, 2, 3]` | | `{ foo: 'bar' }` | `{ "foo": "bar" }` | | `->(x, y, z) { x + y + z }` | `function (x, y, z) { return x + y + z; }` | # Customize Chain You can add custom chain methods in `ExprChain` via `Jsrb::ExprChain.#add_custom_chain`. ```ruby Jsrb::ExprChain.add_custom_chain('log_here', '__tap_log__') js.do! js.expr[:foo][:bar].log_here # __tap_log__(foo['bar']); ``` ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/effective-spa/jsrb. ## License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).