Inesita
Little princess among the frontend frameworks
Star Watch Fork
About
Inesita is a simple, light, Ruby front-end framework. Yes, Ruby, it’s all about Ruby, and its ecosystem.
The main part is component
. Component describes the appearance and behavior of the page.
This is the core of Inesita. Components can be used alone and mounted into any DOM element. You don’t need to use the entire stack.
Moreover, component
supports injections
.
You can inject any other classes into the main component, and they will be available to all nested components. For example, you can inject a Store
class to store your application data. Of course, the implementation of store
depends entirely on you.
Another example of the injection is router
. Router takes care of all navigation stuff, like rendering component tree depending on browser URL, changing URL, etc.
Install
To install and try Inesita, you’ll need a working Ruby environment. Follow these steps to install and launch sample Inesita application:
$ gem install inesita
$ inesita new inesita_app
$ cd inesita_app
$ bundle exec inesita server
What are we doing ?
- Install inesita gem
- Generate sample application named
inesita_app
- Change directory to
inesita_app
- Run server
- Now we can go to http://localhost:9292
As we can see everything works awesome. Let’s take a closer look at our application.
Application
Let’s figure out what’s actually happening. Main file is app/application.rb
.
This is where all the magic starts.
# require Inesita
require 'inesita'
require 'inesita-router'
# require main parts of application
require 'router'
require 'store'
# require all components
require_tree './components'
# when document is ready render application to <body>
class Application
include Inesita::Component
inject Router
inject Store
def render
div.container do
component NavBar
component router
end
end
end
Inesita::Browser.ready? do
Application.mount_to(Inesita::Browser.body)
end
Here you can require all project files, external gems, etc.
require 'inesita'
- require our frameworkrequire 'inesita-router'
- we’ll need a router to handle URLsrequire 'router'
- require our configured routerrequire 'store'
- require our storerequire_tree './components'
- require all other components in thecomponents
directory
Application
is the main component. As we can see, we’re injecting Router
and Store
,
so they will be available throughout the entire component tree as router
and store
.
In store
we will store all application state and data,
router
will render our component tree depending on browser URL.
render
method defines how Inesita will render our components.
It’s like a layout: NavBar
and router
will be rendered within the div
with container
class.
Last part is mounting our application. When document is ready, our main component will be mounted into <body></body>
tag.
Now let’s take a closer look at component.
Component
As you already know, component
is at the core of Inesita. We have seen the main Application
component,
now let’s take a look at the Home
component.
class Home
include Inesita::Component
def render
div.jubmotron.text_center do
img src: '/static/inesita-rb.png'
h1 do
text "Hello I'm Inesita"
end
component Counter, props: {header: 'This is a sample counter'}
end
end
end
This is simple component
that renders bootstrap
jumbotron with a few headers.
We’re using a nice and simple DSL to render HTML. No HTML files here, only Ruby.
So, Home
is a very simple component that only renders HTML.
As you can see, we can pass class
to define classes of elements, but also id
, onclick
, and other element attributes.
Notice that you can use a shortcut for element classes like div.jumbotron
.
In this case div
will include jumbotron
class and text-center
class.
You can also do div.example!
- the exclamation mark means that example
is an id
instead of a class
.
This component also renders a Counter
child component, and passes to it some props
.
Now let’s take a look at the Counter
component.
class Counter
include Inesita::Component
def inc
store.increase
render!
end
def dec
store.decrease
render!
end
def render
h4 do
text props[:header]
end
div.input_group do
span.input_group_btn do
button.btn.btn_default, onclick: method(:dec) do
text '-'
end
end
input.form_control type: "text", value: store.counter, disabled: true
span.input_group_btn do
button.btn.btn_default, onclick: -> { inc } do
text '+'
end
end
end
end
end
Wow, there is a lot of new things. Let start with the render
method.
In h4
we render the header text from props
- things that were passed from the parent component.
Next one is a button. In this case the dec
method describes onclick
behavior.
This method invokes methods from store
, and renders out the component tree.
The render!
method re-renders the entire component tree. No worries, there is virtual-dom
that will only render differences to the actual DOM. It’s fast.
Same thing happens with the inc
method and the +
button, but we’re using a function
notation to invoke the dec
method.
In the middle there is an input field that displays the counter value from store
.
Of course you can store component state in instance variables, it depends on you.
Now we need to take a look at injections
.
Injections
Injections are simply other classes that are included in our component tree.
In our example we have Store
and Router
injections.
Take a look at our Store
.
class Store
include Inesita::Injection
attr_accessor :counter
def init
@counter = 0
end
def increase
@counter += 1
end
def decrease
@counter -= 1
end
end
This is an injection
example. We are including Inesita::Injection
in order to be able to inject this class into the root component.
In this example, we’re storing counter value.
The init
method initializes our store. At the beginning we’re setting the counter value to 0.
The increase
method increments current counter value by 1
The decrease
method decrements current counter value by 1
There is also attr_accessor :counter
so we can access that value with store.counter
.
Simple! Right?
You can inject other stores, but also things like dispatchers depending on the architecture you want to obtain.
Router
Router
is a component
, but also an injection
. It’s a separate gem named inesita-router
.
With router you are able to render the component tree depending on current URL.
class Router
include Inesita::Router
def auth
unless store.logged_in?
go_to('/login')
end
end
def routes
route '/', to: Home
route '/description', to: Description
route '/secret', to: List, on_enter: method(:auth)
route '/login', to: Login
end
end
This class describes how our router will work.
First of all, it includes Inesita::Router
.
In the routes
method, we provide information about which components are rendered on what URL.
On the root path, which is /
, we render the Home
component.
On /description
URL, the router will render the Description
component.
On /secret
URL, the router will check if the user is logged in. If not, it will redirect to /login
.
On /login
URL, the router will render the Login
component.
You can pass props
to child compontents like always.
Router as an injection
provides a few useful methods:
url_for(:name)
method returns a URL for a given route.current_url?(:name)
method returnstrue
if a given route is currently set.
When we’re using inesita-router
, onclick
events are handled by the router automatically.
DOM DSL
There are few things about writing components with DSL worth to mention:
Data attributes
Sometimes you want to write some data into DOM.
You can use data
attribute for that.
To archive that check an example.
class Home
include Inesita::Component
def render
div.jubmotron do
ul do
li data: { id: "1", title: "Title #1" } do
"Title #1"
end
li data: { id: "2", title: "Title #1" } do
"Title #2"
end
li data: { id: "3", title: "Title #1" } do
"Title #3"
end
end
end
end
end
Hooks
If you want to manipulate a DOM element after or before its mounted you can use hooks.
There are two of them.
‘hook’ is executes just after node is attached to DOM, and
unhook
is executes just before node is removed from DOM.
Here is an example.
class Home
include Inesita::Component
def after_mount(node)
# do something with node after mount to dom
puts "I'm mounted #{node}"
end
def before_remove(node)
# do something with node before remove
puts "I'm removed #{node}"
end
def render
div.jubmotron hook: hook(:after_mount), unhook: unhook(:before_remove) do
"Hello"
end
end
end
Awesome Inesita
Addons
- Live Reload · source code 📖
- Router · source code 📖
Applications
- Asciify Me · Live demo 👀 · source code 📖
- DBMonster · Live demo 👀 · source code 📖
- Example Application · Live demo 👀 · source code 📖
- Inesid · Live demo 👀 · source code 📖
- Sprint Poker Frontend · Live demo 👀 · source code 📖
- TodoMVC · Live demo 👀 · source code 📖
- WebAudio Playground · Live demo 👀 · source code 📖
Contribution
Your contributions and suggestions are welcome ♡.