module WrapIt # # Methods for manipulationg with HTML class. For internal usage. # You should not include this class directly - subclass from # `WrapIt::Base` instead. # # @author Alexey Ovchinnikov # module HTMLClass extend DerivedAttributes def self.included(base) base == Base || fail( TypeError, "#{self.class.name} can be included only into WrapIt::Base" ) base.extend ClassMethods end # # Sanitize HTML class list. Arguments list flatten and filtered for only # Strings and Symbols. Duplicates are removed. # # @override sanitize([html_class, ...]) # @param html_class [Object] HTML class # # @return [Array] sanitized HTML classes list def self.sanitize(*args) args .flatten .map { |a| a.is_a?(String) || a.is_a?(Symbol) ? a.to_s : nil } .compact .uniq end # # html class getter. # # @return [Array] array of html classes of element. def html_class @options[:class] end # # HTML class prefix getter. # # @return [String] HTML class prefix. def html_class_prefix self.class.html_class_prefix end # # Sets html class(es) for element. # @param value [Symbol, String, Array] HTML class or list # of classes. All classes will be converted to Strings, duplicates are # removed. # @return [void] # # @example # element.html_class = [:a, 'b', ['c', :d, 'a']] # element.html_class #=> ['a', 'b', 'c', 'd'] def html_class=(value) @options[:class] = [] add_html_class(value) end # # Adds html class(es) to element. Chaining allowed. All classes will be # converted to Strings, duplicates are removed. # @override add_html_class([[html_class], ...]) # @param html_class [Symbol, String, Array] # HTML class or list of HTML classes. # @return [self] # # @example # element.html_class = 'a' # element.add_html_class :b, :c, ['d', :c, :e, 'a'] # element.html_class #=> ['a', 'b', 'c', 'd', 'e'] def add_html_class(*args) if @options.key?(:class) @options[:class].is_a?(Array) || options[:class] = [options[:class]] args += @options[:class] end @options[:class] = HTMLClass.sanitize(*args) self # allow chaining end # # Removes html class(es) from element. Chaining allowed. # @override add_html_class([[html_class], ...]) # @param html_class [Symbol, String, Regexp, Array] # HTML class or list of HTML classes. # @return [self] # # @example # element.add_html_class %w(a b c d e) # element.remove_html_class :b, ['c', :e] # element.html_class #=> ['a', 'd'] def remove_html_class(*args) args.flatten! re = [] args.reject! { |c| c.is_a?(Regexp) && re << c && true } args = args.uniq.map { |c| c.to_s } args.size > 0 && @options[:class].reject! { |c| args.include?(c) } re.is_a?(Array) && re.each do |r| @options[:class].reject! { |c| r.match(c) } end self # allow chaining end # # Determines whether element contains class, satisfied by conditions, # specified in method arguments. # # There are two forms of method call: with list of conditions as arguments # and with block for comparing. Method makes comparison with html class # untill first `true` return value or end of list. All conditions should # be satisfied for `true` return of this method. # # In first form, each argument treated as condition. Condition can be a # `Regexp`, so html classes of element tested for matching to that # regular expression. If condition is an `Array` then every class will be # tested for presence in this array. If condition is `Symbol` or `String` # classes will be compared with it via equality operator `==`. # # In second form all arguments are ignored and for each comparison given # block called with html class as argument. Block return value then used. # # @overload html_class([condition, ...]) # @param condition [] # condition for comparison. # # @overload html_class(&block) # @yield [html_class] Gives each html class to block. You should return # `true` if element contains this html class. # @yieldparam html_class [String] html class to inspect. # @yieldreturn [Boolean] whether element has html class. # # @return [Boolean] whether element has class with specified conditions. # # @example with `Symbol` or `String` conditions # element.html_class = [:a, :b, :c] # element.html_class?(:a) #=> true # element.html_class?(:d) #=> false # element.html_class?(:a, 'b') #=> true # element.html_class?(:a, :d) #=> false # # @example with `Regexp` conditions # element.html_class = [:some, :test] # element.html_class?(/some/) #=> true # element.html_class?(/some/, /bad/) #=> false # element.html_class?(/some/, :test) #=> true # # @example with `Array` conditions # element.html_class = [:a, :b, :c] # element.html_class?(%w(a d)) #=> true # element.html_class?(%w(e d)) #=> false # # @example with block # element.html_class = [:a, :b, :c] # element.html_class? { |x| x == 'a' } #=> true def html_class?(*args, &block) args.all? { |c| inspect_class(:any?, c, &block) } end # # Determines whether element doesn't contains class, satisfied by # conditions, specified in method arguments. # # @see html_class? def no_html_class?(*args, &block) args.all? { |c| inspect_class(:none?, c, &block) } end protected def add_default_classes add_html_class(self.class.collect_derived(:@html_class)) end private def inspect_class(with, value = nil, &block) if block_given? @options[:class].send(with, &block) else case when value.is_a?(Regexp) @options[:class].send(with) { |c| value.match(c) } when value.is_a?(String) || value.is_a?(Symbol) @options[:class].send(with) { |c| value.to_s == c } when value.is_a?(Array) @options[:class].send(with) { |c| value.include?(c) } else false end end end # # Class methods to include # module ClassMethods # # @dsl # Adds default html classes, thats are automatically added when element # created. # @override html_class([html_class, ...]) # @param html_class [String, Symbol, Array] HTML class. # Converted to `String` # # @return [void] def html_class(*args) @html_class.nil? || args += @html_class @html_class = HTMLClass.sanitize(*args) end # # @dsl # Sets HTML class prefix. It used in switchers and enums # @param prefix [String] HTML class prefix # # @return [void] def html_class_prefix(prefix = nil) return get_derived(:@html_class_prefix) || '' if prefix.nil? prefix.is_a?(String) || prefix.is_a?(Symbol) || fail( ArgumentError, 'prefix should be a String or Symbol' ) @html_class_prefix = prefix.to_s end end end end