HTML-slicer¶ ↑
A little gem for Rails 3+ helps you implement a smart way to split text/HTML content into pages. This is a short way to create ‘pageable’ views for models’ string attributes or raw String objects. Optionally it can ‘resize’ on a fly any HTML elements having width=
or style='width: *px;'
attribute*.
* for example <iframe> embeddings from YouTube or images or any other objects.
Features¶ ↑
Clean¶ ↑
Inspired by Kaminari. 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+
-
Rails 3+
-
Haml 3+
Install¶ ↑
Put this line in your Gemfile:
gem 'html_slicer'
Dependencies (for versions 0.2.0+ & Rails 4.2+)
gem 'html-scanner', :github => 'addagger/html-scanner' # (fork from Jakob Skjerning, 'koppen/html-scanner')
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 as a symbol.
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 the 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 example:
@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" # Page 1 @article_paged.slice!(2) # Page 2 # => "ak the silence" @article_paged.slice!(4) # Page 4 # => "rld <iframe width="300" height="169" src="http://www.youtube.com/embed/ms0bd_hCZsk" frameborder="0" allowfullscreen></iframe>"
The .slice!()
method accepts slice number (page) as an integer. Blank argument (or nil
) assumes it is number 1
.
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 one unit.
-
:maximum
is aInteger
number of units to be in 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
- aInteger
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
html_scanner
code). Look at github.com/koppen/html-scanner/blob/master/lib/html/node.rb
This is a very useful utility to navigate via HTML content. Read native documentation for details and thank you, Assaf Arkin for that legendary code being with Rails up to 4.1 since 2006.
For example: ID for
<hr class="break">
tag is a hash:{:tag => "hr", :attributes => {:class => "break"}}
-
Resizing options¶ ↑
-
:width
is a Integer 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.