Project

glia

0.0
No commit activity in last 3 years
No release in over 3 years
There's a lot of open issues
Manage App Layouts.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

~> 1.7
~> 5.0
~> 10.0
 Project Readme

Glia

As your app grows, you may find that a lot of logic is necessary in your views. Implementing a proper view layer, such as Cells, will help to reduce complexity by encapsulating this logic into classes for specific parts of the page.

But what happens when you need somewhere to manage the logic determining what cells go where and on what page? This is where Glia comes in handy, it provides an additional layout layer to manage your views.

Installation

Add this line to your application's Gemfile:

gem 'glia'

And then execute:

$ bundle

Or install it yourself as:

$ gem install glia

How do I?

Have a layout-related problem and want to know if/how Glia can help?

If the whole concept doesn't make sense to you, maybe your app is simple enough not to need it, or maybe it would help for you to know that Glia is heavily inspired by Magento's layout system

Usage

Cells

A Cell is a view object that renders a certain part of the page.

Glia does not provide an implementation of cells, but expects that you have this concept as part of your project.

You can use a gem such as Cells, to provide this interface.

Glia provides a mixin, Glia::Cell that you should include in your cells to provide methods that help with things such as setting/getting child cells, and accessing the layout layer.

Layout files

Layout files are written with a DSL that is designed to help describe the layout of your pages. e.g.

# layout/frontend/default.rb
Glia.area(:frontend) do 
  view_namespace Fixtures::View
  handle :default do
    cell name: :root, class: :'core/html', template_name: 'root', missing_accessor: 'ignore_me' do
      cell name: :header, class: :template, template_name: 'header'
      cell name: :sidebar, class: :list do
        # ...
      end
      cell name: :footer, class: :template, template_name: 'footer'
    end
  end
  handle :cake_view do
    reference name: :root do
      cell name: :details, class: Fixtures::View::Template, template_name: 'cake_details', position: :content do
        cell name: :specifications, class: :list do
          cell name: :cake_specs, class: 'Template', template_name: 'cake/specs'
          cell name: :cake_ingredients, class: :template, template_name: 'cake/ingredients'
        end
      end
    end
  end
  handle :pavlova_view, :new_view do
    reference name: :specifications do
      remove name: :cake_specs
    end
    reference name: :cake_ingredients, template_name: 'cake/pavlova_ingredients' do
      action name: :add_ingredient, args: ['Eggs', '6 Large']
    end
  end
end

Cake View

Pav View

View Namespace (Optional)

Pass a class/module. Any cell created within this area definition using a string or symbol for class will use this namespace when getting the actual class.

Handle

Handles are key tool for customising your layout per page. Each page in your app will have a set of handles, which consist of an array of symbols. e.g.

[:default, :cake_view, :pavlova_view]

It is often a good idea to have the handles automatically generated from the controller, including a default handle, one for the controller name, and another for the controller#action name. You can also have your controller add custom handles depending on the request, or the type of item being viewed. e.g.

[:default, :quotes, :quotes_show, :quotes_show_AJAX, :quote_show_TYPE_PLUMBING]

If your project has a concept of modules (that encapsulates a feature and contains controllers), then it would also be a very good idea to include the module name in your handles. e.g.

[:default, :sales_quotes, :sales_quotes_show]

The order of the set of handles is important. Glia will start at the first handle (e.g. :default) when building the layout. Each successive handle will modify the layout generated from the previous handle. In the DSL example above, if our handles were [:default, :cake_view, :pavlova_view], then the template_name for the cake_ingredients cell will be 'cake/pavlova_ingredients', and we won't have a :cake_specs cell on the page.

Using this system, we can have considerable flexibility in the layout based on the factors that we use to determine the handles. For example, we can add or remove menu items from a navigation cell if the customer is logged in, or if we are on the customer account page. We can add or remove certain cells on the page based on the type of product that we are viewing.

Generating the handles for a request/action and passing them to the layout during rendering is decribed in further detail in the Integration section.

All layout methods are contained within a handle, and as such all other methods must appear inside a handle block.

Cell

The cell method places a cell inside the layout. The layout starts with a single cell, which by convention is named :root. All other cells on the page are descendants of the root cell, and most likely will be rendered as part of rendering the root cell, being contained in its output.

  • name is the name of the cell in the layout. Each cell must have its own unique name within the layout
  • class is a code that the layout system uses to figure out what class to instantiate when building the cell. This can either be:
    • A String representing the class name, either fully qualified or from the view_namespace (see Integration)
    • A Symbol Representing the class name from the view_namespace, separated by '/' for each sub-namespace.
    • A Class
  • position This is a code that should be unique within the parent cell. If omitted, will default to the same value as name. This code is used to control where the cell is rendered within the parent cell, using the cell method. For example, the 'cake_details' template file might have a call to cell(:specifications).render in a specific place to render that specific child cell.
  • Any other parameters passed to cell will be passed through to the cell's constructor.
  • Anything in the block will operate on this cell. E.g. calling cell within the block will create a child cell.

Reference

The reference method refers to a cell that has been added in a previous (or subsequent) handle. Any parameter will overwrite the same parameter defined in a previous handle. Any contents of the block will operate on the previously defined cell.

The technical difference from the cell method is that instead of requiring the class parameter, it disallows it, and a reference without a matching cell would be ignored rather than creating a cell in the layout.

Action

This method will run the specified method (with the given arguments) on the cell immediately after it is initialized.

  • name The name of the method
  • args An array of arguments that will be passed to the method.

Areas

An 'area' is a distinct layout that is completely separate from another area. For example many apps would have a :frontend area, and an :admin area.

If you app code is organised into modules, you may wish to keep a layout file in each module. This way, the layout files can each place cells related to their own modules on any page of the app.

Just wrap the DSL with Glia.area(:area_name) do .. end, then make sure the layout files are required as part of your app's bootstrap process.

Glia.area(:frontend) do 
  handle :pavlova_view do
    reference name: :specifications do
      remove name: :cake_specs
    end
    reference name: :cake_ingredients, template_name: 'cake/pavlova_ingredients' do
      action name: :add_ingredient, args: ['Eggs', '6 Large']
    end
  end
end

Integration

Generate the handles

The handles will be generated in the controller based on the request/loaded objects/controller. Examples to come.

Create a layout

Pick an 'area' to render, e.g. :frontend or :admin, and pass this into the layout method along with the handles.

layout = Glia.layout(:frontend, [:default, :cake_view, :pavlova_view])
```

This returns an instance of Glia::Layout

### Render the layout.

Get the `:root` cell from the layout, and render it. The `render` method is provided by your view layer library/gem. 
Substitute the method name that you have. 

```ruby
layout.cell(:root).render
```

That is all. Your root cell's rendering process should be such that the child cells are picked out and rendered,
and those child cells pick out and render their child cells, and so on until the whole layout tree is rendered. 

## Suggestions

### Suggested Cell Types

* `Template` A generic cell that has a `template_name` and a data passed into its constructor in key/value pairs.
  It would render the template, using the data as locals. 
* `List` A generic cell that has no template, but instead renders all child cells 
  (DSL to control order of child cells to come soon). 
  These can be very handy for providing areas such as sidebars, where any number of cells can be added or removed 
  without having to have a `cell(:name).render` helper called for each specific child cell 
* `Html::Head` A cell with specific methods for including asset tags etc.
* Specific classes for cells that have their own logic. E.g. `Product::Price`, `Category::List` 

### Getting data to cells

Try one or more of these options:

* Pass the data as locals to each cell or child cell as you render it. 
* Create a registry (using e.g. [RequestStore](https://github.com/steveklabnik/request_store)), which the controller can
  pass data to, and the cells can read data from. 
* If more flexibility is required, the controller can pull a specific cell from the layout, and call a method on it. 

### Customise the ViewFactory

Example for Customised Lotus Views

```ruby
module Frontend
  class Application < Lotus::Application

    def renderer
      @custom_renderer ||= Client::RenderingPolicy.new(configuration)
    end

    configure do
      # ...
    end

  end
end
```

```ruby
require 'glia'
class Client::RenderingPolicy < Lotus::RenderingPolicy

  class ViewFactory < Glia::ViewFactory
    def instantiate(klass, definition, *args)
      #@todo: Allow blocks/cells to subscribe to registry values.
      locals = args[0].nil? ? definition : definition.merge(args[0])
      klass.new({format: :html}.merge(locals))
    end
  end

  def _render_action(action, response)
    @area ||= @namespace.name.downcase.to_sym
    if successful?(response)
      if response[BODY].empty?
        RequestStore.store[:action_exposures] = action.exposures
        layout = Glia.layout(@area, action.handles)
        layout.view_factory = ViewFactory.new
        layout.cell(:root, action.exposures).render
      else
        Lotus::Views::NullView.new(response[BODY]).render(action.exposures)
      end
    end
  end

end
```

## Theme Inheritance
In addition to areas, Glia provides a means to override the layouts with different 'themes'. 

For example. If your project serves multiple sites that each need a different header, you can achieve this with themes. 

```ruby
Glia::UpdateRegistry.area(:frontend, :default) do
  handle :default do
    cell name: :root, class: :html, template_name: 'root' do
      cell name: :header, class: :template, template_name: 'header'
      cell name: :footer, class: :template, template_name: 'footer'
    end
  end
end

Glia::UpdateRegistry.area(:frontend, :au) do
  handle :default do
    cell name: :root, class: :html, template_name: 'root' do
      cell name: :header, class: :template, template_name: 'header_au'
      remove name: :footer
    end
  end
end
```

When you get the layout, just specify which themes you want to merge. 

```ruby
layout = Glia.layout(:frontend, [:default, :product_view], theme_inheritance: [:default, :au])
```  

There is no limit to the number of themes you can use in the inheritance chain. 
It begins with the first theme, and applies updates from each successive theme.

## Notes
* When the DSL is parsed, the result is a simple hash of values that describes the layout. 

## Contributing

1. Fork it ( https://github.com/danelowe/glia/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request