module Deface class Override include Deface::TemplateHelper cattr_accessor :all, :actions attr_accessor :args @@all ||= {} @@actions = [:remove, :replace, :insert_after, :insert_before, :insert_top, :insert_bottom] # 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 # * :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 # # ==== 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. def initialize(args) @args = args raise(ArgumentError, "Invalid action") if self.action.nil? raise(ArgumentError, ":virtual_path must be defined") if args[:virtual_path].blank? key = args[:virtual_path].to_sym @@all[key] ||= {} @@all[key][args[:name].to_s.parameterize] = self end def selector @args[self.action] end def name @args[:name] 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] end Deface::Parser.erb_markup!(erb) end def source_element @converted_source ||= Deface::Parser.convert(source.clone).to_s end def disabled? @args.key?(:disabled) ? @args[:disabled] : false end def end_selector @args[:closing_selector] end # applies all applicable overrides to given source # def self.apply(source, details) overrides = find(details) @enable_logging ||= (defined?(Rails) == "constant" ? true : false) if @enable_logging && overrides.size > 0 Rails.logger.info "\e[1;32mDeface:\e[0m #{overrides.size} overrides found for #{details[:virtual_path]}" end unless overrides.empty? doc = Deface::Parser.convert(source) overrides.each do |override| if override.disabled? if @enable_logging Rails.logger.info "\e[1;32mDeface:\e[0m #{override.name} is disabled" end next end if override.end_selector.blank? # single css selector matches = doc.css(override.selector) if @enable_logging Rails.logger.info "\e[1;32mDeface:\e[0m #{override.name} matched #{matches.size} times with #{override.selector}" end matches.each do |match| case override.action when :remove match.replace "" when :replace match.replace override.source_element when :insert_before match.before override.source_element when :insert_after match.after override.source_element when :insert_top match.children.before(override.source_element) when :insert_bottom match.children.after(override.source_element) end end else # targeting range of elements as end_selector is present starting = doc.css(override.selector).first if starting && starting.parent ending = starting.parent.css(override.end_selector).first else ending = doc.css(override.end_selector).first end if starting && ending elements = select_range(starting, ending) if override.action == :replace starting.before(override.source_element) end #now remove all matched elements elements.map &:remove end end end source = doc.to_s Deface::Parser.undo_erb_markup!(source) end source end # finds all applicable overrides for supplied template # def self.find(details) return [] if @@all.empty? || details.empty? result = [] virtual_path = details[:virtual_path] result << @@all[virtual_path.to_sym].try(:values) result.flatten.compact end private # finds all elements upto closing sibling in nokgiri document # def self.select_range(first, last) first == last ? [first] : [first, *select_range(first.next, last)] end end end