module Deface
class Override
include TemplateHelper
include OriginalValidator
extend Applicator::ClassMethods
extend Search::ClassMethods
cattr_accessor :actions, :_early
attr_accessor :args
@@_early = []
@@actions = [:remove, :replace, :replace_contents, :surround, :surround_contents, :insert_after, :insert_before, :insert_top, :insert_bottom, :set_attributes, :add_to_attributes, :remove_from_attributes]
@@sources = [:text, :erb, :haml, :partial, :template]
# Initializes new override, you must supply only one Target, Action & Source
# parameter for each override (and any number of Optional parameters).
#
# ==== Target
#
# * :virtual_path - The path of the template / partial where
# the override should take effect eg: "shared/_person", "admin/posts/new"
# this will apply to all controller actions that use the specified template
#
# ==== Action
#
# * :remove - Removes all elements that match the supplied selector
# * :replace - Replaces all elements that match the supplied selector
# * :replace_contents - Replaces the contents of all elements that match the supplied selector
# * :surround - Surrounds all elements that match the supplied selector, expects replacement markup to contain <%= render_original %> placeholder
# * :surround_contents - Surrounds the contents of all elements that match the supplied selector, expects replacement markup to contain <%= render_original %> placeholder
# * :insert_after - Inserts after all elements that match the supplied selector
# * :insert_before - Inserts before all elements that match the supplied selector
# * :insert_top - Inserts inside all elements that match the supplied selector, before all existing child
# * :insert_bottom - Inserts inside all elements that match the supplied selector, after all existing child
# * :set_attributes - Sets attributes on all elements that match the supplied selector, replacing existing attribute value if present or adding if not. Expects :attributes option to be passed.
# * :add_to_attributes - Appends value to attributes on all elements that match the supplied selector, adds attribute if not present. Expects :attributes option to be passed.
# * :remove_from_attributes - Removes value from attributes on all elements that match the supplied selector. Expects :attributes option to be passed.
#
# ==== Source
#
# * :text - String containing markup
# * :partial - Relative path to partial
# * :template - Relative path to template
#
# ==== Optional
#
# * :name - Unique name for override so it can be identified and modified later.
# This needs to be unique within the same :virtual_path
# * :disabled - When set to true the override will not be applied.
# * :original - String containing original markup that is being overridden.
# If supplied Deface will log when the original markup changes, which helps highlight overrides that need
# attention when upgrading versions of the source application. Only really warranted for :replace overrides.
# NB: All whitespace is stripped before comparsion.
# * :closing_selector - A second css selector targeting an end element, allowing you to select a range
# of elements to apply an action against. The :closing_selector only supports the :replace, :remove and
# :replace_contents actions, and the end element must be a sibling of the first/starting element. Note the CSS
# general sibling selector (~) is used to match the first element after the opening selector.
# * :sequence - Used to order the application of an override for a specific virtual path, helpful when
# an override depends on another override being applied first.
# Supports:
# :sequence => n - where n is a positive or negative integer (lower numbers get applied first, default 100).
# :sequence => {:before => "override_name"} - where "override_name" is the name of an override defined for the
# same virutal_path, the current override will be appplied before
# the named override passed.
# :sequence => {:after => "override_name") - the current override will be applied after the named override passed.
# * :attributes - A hash containing all the attributes to be set on the matched elements, eg: :attributes => {:class => "green", :title => "some string"}
#
def initialize(args, &content)
if Rails.application.try(:config).try(:deface).try(:enabled)
unless Rails.application.config.deface.try(:overrides)
@@_early << args
warn "[WARNING] You no longer need to manually require overrides, remove require for '#{args[:name]}'."
return
end
else
warn "[WARNING] You no longer need to manually require overrides, remove require for '#{args[:name]}'."
return
end
raise(ArgumentError, ":name must be defined") unless args.key? :name
raise(ArgumentError, ":virtual_path must be defined") if args[:virtual_path].blank?
args[:text] = content.call if block_given?
virtual_key = args[:virtual_path].to_sym
name_key = args[:name].to_s.parameterize
self.class.all[virtual_key] ||= {}
if self.class.all[virtual_key].has_key? name_key
#updating exisiting override
@args = self.class.all[virtual_key][name_key].args
#check if the action is being redefined, and reject old action
if (@@actions & args.keys).present?
@args.reject!{|key, value| (@@actions & @args.keys).include? key }
end
#check if the source is being redefined, and reject old action
if (@@sources & args.keys).present?
@args.reject!{|key, value| (@@sources & @args.keys).include? key }
end
@args.merge!(args)
else
#initializing new override
@args = args
raise(ArgumentError, ":action is invalid") if self.action.nil?
end
self.class.all[virtual_key][name_key] = self
expire_compiled_template
self
end
def selector
@args[self.action]
end
def name
@args[:name]
end
def sequence
return 100 unless @args.key?(:sequence)
if @args[:sequence].is_a? Hash
key = @args[:virtual_path].to_sym
if @args[:sequence].key? :before
ref_name = @args[:sequence][:before]
if self.class.all[key].key? ref_name.to_s
return self.class.all[key][ref_name.to_s].sequence - 1
else
return 100
end
elsif @args[:sequence].key? :after
ref_name = @args[:sequence][:after]
if self.class.all[key].key? ref_name.to_s
return self.class.all[key][ref_name.to_s].sequence + 1
else
return 100
end
else
#should never happen.. tut tut!
return 100
end
else
return @args[:sequence].to_i
end
rescue SystemStackError
if defined?(Rails)
Rails.logger.error "\e[1;32mDeface: [WARNING]\e[0m Circular sequence dependency includes override named: '#{self.name}' on '#{@args[:virtual_path]}'."
end
return 100
end
def action
(@@actions & @args.keys).first
end
def source
erb = if @args.key? :partial
load_template_source(@args[:partial], true)
elsif @args.key? :template
load_template_source(@args[:template], false)
elsif @args.key? :text
@args[:text]
elsif @args.key? :erb
@args[:erb]
elsif @args.key?(:haml) && Rails.application.config.deface.haml_support
haml_engine = Deface::HamlConverter.new(@args[:haml])
haml_engine.render
end
erb
end
def source_element
Deface::Parser.convert(source.clone)
end
def disabled?
@args.key?(:disabled) ? @args[:disabled] : false
end
def end_selector
return nil if @args[:closing_selector].blank?
"#{self.selector} ~ #{@args[:closing_selector]}"
end
def attributes
@args[:attributes] || []
end
def digest
Digest::MD5.new.update(@args.to_s).hexdigest
end
def self.all
Rails.application.config.deface.overrides.all
end
def self.digest(details)
overrides = self.find(details)
Digest::MD5.new.update(overrides.inject('') { |digest, override| digest << override.digest }).hexdigest
end
private
# check if method is compiled for the current virtual path
#
def expire_compiled_template
if compiled_method_name = ActionView::CompiledTemplates.instance_methods.detect { |name| name =~ /#{args[:virtual_path].gsub(/[^a-z_]/, '_')}/ }
#if the compiled method does not contain the current deface digest
#then remove the old method - this will allow the template to be
#recompiled the next time it is rendered (showing the latest changes)
unless compiled_method_name =~ /\A_#{self.class.digest(:virtual_path => @args[:virtual_path])}_/
ActionView::CompiledTemplates.send :remove_method, compiled_method_name
end
end
end
end
end