html_slicer
A little gem for Rails 3 helps you implement a smart way to split textual content. This is a quick approach to create ‘pageable’ views for ActiveRecord Model’s attributes, just independent strings, or any other Ruby classes. Or course it can split HTML content. More over, it can ‘resize’ HTML tags through width=
attribute*.
* Imagine you want to resize <iframe> embeddings from YouTube saved in static content.
Features
Clean
Does not globally pollute Array
, Hash
, Object
or AR::Base
.
Easy to use
Bundle the gem, then your models are ready to implement slicing.
Flexible configuration
Besides global configuration (default), you can create stylized app-level configurations; also any configuration can be tuned for every single implementation.
Flexible options
You can split your text with any Regexp clause: set up which node from your HTML content you want to split, or which you don’t want to. You can also use processor(s) to transform the content before it has to be sliced. Param name, used as a slice number key accessor, can be nested in another param key (nested params). Resizing HTML tags having width=
attribute is a helpful feature too.
ORM & template engine agnostic
Supports Rails 3 and multiple template engines (ERB, Haml).
Modern
The slicer helper outputs the HTML5 <nav> tag by default. In addition, the helper supports Rails 3 unobtrusive Ajax.
Supported versions
-
Ruby 1.9.2, 1.9.3
-
Rails 3.0.x, 3.1, 3.2
-
Haml 3+
Install
Put this line in your Gemfile:
gem 'html_slicer'
Then bundle:
% bundle
Implementation
Basic approach
slice <method_name>, <configuration>, [:config => <:style>]*
where:
-
<method_name> - any method name which returns source String (can be called with .send()).
-
<configuration> - Hash of configuration options and/or
:config
parameter.
Basic example
class Article < ActiveRecord::Base
slice :content, :as => :paged, :slice => {:maximum => 20}, :resize => {:width => 300}
end
where:
-
:content
is an attribute accessor for Article which returns a target String object. -
:as
is a name of basic accessor for result. -
:slice
is a hash ofslicing options
as a part of configuration. -
:resize
is a hash ofresizing options
as a part of configuration. -
:cache_to
is an accessor name used to store the cache.True
value make it by default.
You can define any configuration key you want. Otherwise, default configuration options (if available) will be picked up automatically.
Console:
@article = Article.find(1)
@article.content
# => "Words like violence break the silence\r\nCome crashing in into my little world\r\n<iframe width=\"560\" height=\"315\" src=\"http://www.youtube.com/embed/ms0bd_hCZsk\" frameborder=\"0\" allowfullscreen></iframe>\r\nPainful to me, pierce right through me\r\nCan't you understand, oh my little girl?"
@article_paged = @article.paged
# => "Words like violence bre"
@article_paged.slice!(2)
# => "ak the silence"
@article_paged.slice!(4)
# => "rld
<iframe width="300" height="169" src="http://www.youtube.com/embed/ms0bd_hCZsk" frameborder="0" allowfullscreen></iframe>"
The .slice!()
method accepts number of slice (page). No argument (or nil
) assumes it is number 1
. Passed slice number is remembered.
Configuration options
All configuration keys:
-
:as
is a name of basic accessor for slicedobject
(result). -
:slice
is a hash of slicing options. -
:resize
is a hash of resizing options. -
:processors
- processors names. -
:window
- parameter for ActionView: The “inner window” size (4 by default). -
:outer_window
- parameter for ActionView: The “outer window” size (0 by default). -
:left
- parameter for ActionView: The “left outer window” size (0 by default). -
:right
- parameter for ActionView: The “right outer window” size (0 by default). -
:params
- parameter for ActionView: url_for parameters for the links (:controller, :action, etc.) -
:param_name
- parameter for ActionView: parameter name for slice number in the links. AcceptsSymbol
,String
,Array
. -
:remote
- parameter for ActionView: Ajax? (false by default) -
:config
- special key for using stylized configuration (premature configuration).
Slicing options
-
:unit
is aRegexp/String/Hash
description of text units counted to split the text by slices.
When value is a Hash
, it assumes the unit is a HTML tag (look at :only/:except
options for details). Undefined value or nil
assumes it by default regular expression /&#?\w+;|\S/
. As you can see it counts any regular character or HTML special character as a unit.
-
:maximum
is aFixnum
number of units to be a one slice.
If :unit
defined as Regexp or String, default value is 300.
If :unit
defined as Hash, default value is 10.
If :unit
is default, default value is 2000.
-
:complete
is aRegexp
description of a character used to complete the slice.
For example, if you want to end the slice with a complete word, using :complete => /s+|z/
the counter would continue the slice until the first whitespace character.
-
:limit
- aFixnum
limit number of slices.
In many cases we just need the first slice to perform it as a partial.
-
:text_break
- a responsible.to_s
value of text breaks between slices.
It can be an ellipsis or any other symbol.
-
:only
is aHash
orArray
of hashes, describes which exactly nodes of HTML content to slice.* -
:except
is aHash
orArray
of hashes, describes which exactly nodes of HTML content NOT to slice.*-
Actually the hash is an argument for HTML::Conditions class (the part of ActionPack’s html_scanner code). Look at github.com/rails/rails/blob/master/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
This is a very flexible utility to navigate via HTML content. Read native documentation for details.
For example: ID for
<hr class="break">
tag is a hash:{:tag => "hr", :attributes => {:class => "break"}}
-
Resizing options
-
:width
is a Fixnum number of pixels as a target value to squeeze the HTML tag. It does the resize automatically proportional with the ‘height=’ (if the tag has one). The percentage values are ignored. -
:only
is aHash
orArray
of hashes, describes which exactly nodes of HTML content to resize. -
:except
is aHash
orArray
of hashes, describes which exactly nodes of HTML content NOT to resize.
Processors
Processors are used to transform the source text before it is sliced. Many of us use various markup languages for dynamic contents. This is basically the same thing. Just create any class and inherit it from HtmlSlicer::Processor, put it in /lib/html_slicer_processors
directory and define its name within the :processors
option.
Example:
# /lib/html_slicer_processors/textilized.rb:
class Textilized < HtmlSlicer::Processor
def make
ERB::Util.textilize(content)
end
end
Processor class has to include make
method, which returns transformed String. The content accessor is a source String. Then you can set the option :processors => :textilized
. The value of option can be Array of names, if you use more than one processor.
Configuration levels
There are three levels of configuration. Missing options are inherited from upper-level ones.
Global configuration
The top level configuration contains necessary options by default:
window = 4
outer_window = 0
left = 0
right = 0
param_name = :slice
You can override/complete global configuration this way:
HtmlSlicer.configure do |config|
config.slice = {:complete => /\s+|\z/, :maximum => 2000}
config.resize = {:width => 300, :only => {:tag => 'iframe'}}
config.window = 5
config.param_name = :any_other_param_name
end
Stylized configuration
Along with common global configuration, you can define stylized configuration passing argument like this:
HtmlSlicer.configure(:paged) do |config|
config.as = :page
config.param_name = :page
end
Missing options are inherited from global one.
Instance configuration
If you implement HtmlSlicer accessor this way:
slice <method_name>, <configuration>, [:config => <:style>]*
you can override/complete any configuration.
By applying :config
key you can use one of stylized configurations:
class Post < ActiveRecord::Base
slice :content, :config => :paged, :slice => {:complete => /\s+/, :maximum => 3000}
end
Through passing the block you get a more flexible approach to configure slicer:
class Post < ActiveRecord::Base
slice :content, :config => :paged, :as => :blabla do |config|
config.slice[:maximum] = 3000
config.resize.merge! :only => {:tag => "p"}
config.processors << :decorated
end
end
Views
Presumption
Model:
class Article < ActiveRecord::Base
slice :content, :as => :paged, :slice => {:maximum => 1000, :complete => /\s+|\z/}
end
Controller:
@article = Article.find(1)
@article_paged = @article.paged.slice!(params[:slice])
On a View just call the slice
helper:
<%= slice @article_paged %>
This will render several ?slice=N
slicing links surrounded by an HTML5 <nav
> tag.
Helpers
-
the
slice
helper method<%= slice @article_paged %>
This would output several slicing links such as
« First ‹ Prev ... 2 3 4 5 6 7 8 9 10 ... Next › Last »
-
specifying the “inner window” size (4 by default)
<%= slicing @article_paged, :window => 2 %>
This would output something like
... 5 6 7 8 9 ...
when 7 is the current page. -
specifying the “outer window” size (0 by default)
<%= slicing @article_paged, :outer_window => 3 %>
This would output something like
1 2 3 4 ...(snip)... 17 18 19 20
while having 20 slices in total. -
outer window can be separately specified by
left
,right
(0 by default)<%= slicing @article_paged, :left => 1, :right => 3 %>
This would output something like
1 ...(snip)... 18 19 20
while having 20 slices in total. -
changing the parameter name (:
param_name
) for the links<%= slicing @article_paged, :param_name => :page %>
This would modify the query parameter name on each links.
-
extra parameters (:
params
) for the link<%= slicing @article_paged, :params => {:controller => 'foo', :action => 'show', :id => 21} %>
This would modify each link’s
url_option
. :controller
and :action
might be the common keys. -
Ajax links (crazy simple, but works perfectly!)
<%= slicing @article_paged, :remote => true %>
This would add
data-remote="true"
to all the links inside. -
the
link_to_next_page
helper method<%= link_to_next_slice @article_paged, 'Next Page' %>
This simply renders a link to the next slice. This would be helpful for creating “Twitter like” pagination feature.
-
the
slice_entries_info
helper method<%= slice_entries_info @article_paged %>
This renders a helpful message with numbers of displayed vs. total entries.
I18n and labels
The default labels for ‘first’, ‘last’, ‘previous’, ‘…’ and ‘next’ are stored in the I18n yaml inside the engine, and rendered through I18n API. You can switch the label value per I18n.locale for your internationalized application. Keys and the default values are listed below. You can override them by adding to a YAML file in your Rails.root/config/locales
directory.
en:
views:
html_slicer:
first: "« First"
last: "Last »"
previous: "‹ Prev"
next: "Next ›"
truncate: "..."
Param name
You can define :param_name
as a symbol
or string
, or as an array
of any object that responses .to_s
method and returns string
. Passing array
is the way to define nested :param_name.
Examples:
:param_name => :page
# means you define params[:page] as a slice key.
:param_name => [:article, :page]
# means you define params[:article][:page] as a slice key.
Caching
Caching implies that resizing/slicing procedures is both time-consuming processes will be started only at once, and once again will be launched only if the target content has been changed or resizing/slicing options has been modified.
For caching, pass the option: :cache_to => true
or :cache_to => :any_other_accessor
within your config definition.
ActiveRecord model
Example:
class Article < ActiveRecord::Base
slice :content, :as => :paged, :slice => {:maximum => 1000, :complete => /\s+|\z/}, :cache_to => :content_page_cache
end
Accessor method .content_page_cache=
used for caching here. The first time when resizing and slicing procedures is called, generated maps will be cached and assigned to .content_page_cache
accessor. Before the article saves itself, assigned dump stuff is recorded like any other attribute of article (callback before_save
is set up).
Of course, attribute is recorded only as a column of the database :), So, before, add the column to a model:
% rails generate migration AddContentPageCacheToArticle content_page_cache:text
% rake db:migrate
Slicing/resizing procedures repeat again only if target content has been changed or options has been modified.
ActiveModel
Example:
class TextModel
attr_accessor :text, :paged_cache
extend HtmlSlicer::Installer
slice :text, :as => :paged, :slice => {:complete => /\s+/, :maximum => 300}, :cache_to => true
def initialize(text)
@text = text
end
end
True
value of :cache_to
option set default cache accessor name consisted of basic accessor name + _page
.
In fact, caching ActiveModel not so significant in most cases, but still works. The next time you call slicing method, the cached resizing/slicing map(s) will be used.
More
Page breaks with links!
Passing the option :text_break => "..."
is for mainly decorative purpose. Within the slice (page), the final textual content is complemented by ellipsis or any other symbol you like. If we’d like to place the URL link at the end, we can pass static link as well (“Read mode” or something…), but what if we’d like to pass dynamic link generated within the view
using the full power of ActionView and @template?
So, we have:
@article = Article.find(1)
@article_paged = @article.paged.slice!(params[:slice])
On a View we call .to_s()
method and pass it the block:
<%= @article_paged.to_s {|slicer, text| text << link_to("Read more", url_for(url), :class => "read-more")} %>
where:
-
slicer
is a slicer object itself. -
text
is a final textual content of the current slice (page) which we complete with the generated link inside the block code.
Slicing a general String or ActiveModel (or any other) object
There is no apecific approach to it. Just extend target class to HtmlSlicer::Installer and call the method slice as described before:
String.extend HtmlSlicer::Installer
String.slice :to_s, :as => :page, :config => :for_string
Questions, Feedback
Message me and I’ll do my best to help everybody. Github (addagger), no Twitter account, Facebook (www.facebook.com/valery.kvon)
Contributing to HtmlSlicer
-
Fork, fix, and then send me a pull request.
Copyright
Copyright © 2012 Valery Kvon. See MIT-LICENSE for further details.