Module: Glimmer::Web::Component

Includes:
DataBinding::ObservableModel
Included in:
AddressForm, AddressPage, ButtonCounter, HelloObserver, HelloParagraph
Defined in:
lib/glimmer/web/component.rb

Defined Under Namespace

Modules: ClassMethods, GlimmerSupersedable

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object



327
328
329
330
331
332
333
334
335
# File 'lib/glimmer/web/component.rb', line 327

def method_missing(method_name, *args, &block)
  if can_handle_observation_request?(method_name)
    handle_observation_request(method_name, block)
  elsif markup_root.respond_to?(method_name, true)
    markup_root.send(method_name, *args, &block)
  else
    super(method_name, *args, &block)
  end
end

Instance Attribute Details

#argsObject (readonly)

Returns the value of attribute args.



194
195
196
# File 'lib/glimmer/web/component.rb', line 194

def args
  @args
end

#markup_rootObject (readonly)

Returns the value of attribute markup_root.



194
195
196
# File 'lib/glimmer/web/component.rb', line 194

def markup_root
  @markup_root
end

#optionsObject (readonly)

Returns the value of attribute options.



194
195
196
# File 'lib/glimmer/web/component.rb', line 194

def options
  @options
end

#parentObject (readonly) Also known as: parent_proxy

Returns the value of attribute parent.



194
195
196
# File 'lib/glimmer/web/component.rb', line 194

def parent
  @parent
end

Class Method Details

.add_component_namespaces_for(klass) ⇒ Object



165
166
167
168
169
# File 'lib/glimmer/web/component.rb', line 165

def add_component_namespaces_for(klass)
  Glimmer::Web::Component.namespaces_for_class(klass).drop(1).each do |namespace|
    Glimmer::Web::Component.component_namespaces << namespace
  end
end

.component_namespacesObject



179
180
181
# File 'lib/glimmer/web/component.rb', line 179

def component_namespaces
  @component_namespaces ||= reset_component_namespaces
end

.for(underscored_component_name) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/glimmer/web/component.rb', line 130

def for(underscored_component_name)
  extracted_namespaces = underscored_component_name.
    to_s.
    split(/__/).map do |namespace|
      namespace.camelcase(:upper)
    end
  Glimmer::Web::Component.component_namespaces.each do |base|
    extracted_namespaces.reduce(base) do |result, namespace|
      if !result.constants.include?(namespace)
        namespace = result.constants.detect {|c| c.to_s.upcase == namespace.to_s.upcase } || namespace
      end
      begin
        constant = result.const_get(namespace)
        return constant if constant&.respond_to?(:ancestors) &&
                           (
                             constant&.ancestors&.to_a.include?(Glimmer::Web::Component) ||
                             # TODO checking GlimmerSupersedable as a hack because when a class is loaded twice (like when loading samples
                             # by reloading ruby files), it loses its Glimmer::Web::Component ancestor as a bug in Opal
                             # but somehow the prepend module remains
                             constant&.ancestors&.to_a.include?(GlimmerSupersedable)
                           )
        constant
      rescue => e
        # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
        result
      end
    end
  end
  raise "#{underscored_component_name} has no Glimmer web component class!"
rescue => e
  Glimmer::Config.logger.debug {e.message}
  Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
  nil
end

.included(klass) ⇒ Object



121
122
123
124
125
126
127
128
# File 'lib/glimmer/web/component.rb', line 121

def included(klass)
  if !klass.ancestors.include?(GlimmerSupersedable)
    klass.extend(ClassMethods)
    klass.include(Glimmer)
    klass.prepend(GlimmerSupersedable)
    Glimmer::Web::Component.add_component_namespaces_for(klass)
  end
end

.interpretation_stackObject



187
188
189
# File 'lib/glimmer/web/component.rb', line 187

def interpretation_stack
  @interpretation_stack ||= []
end

.namespaces_for_class(m) ⇒ Object



171
172
173
174
175
176
177
# File 'lib/glimmer/web/component.rb', line 171

def namespaces_for_class(m)
  return [m] if m.name.nil?
  namespace_constants = m.name.split(/::/).map(&:to_sym)
  namespace_constants.reduce([Object]) do |output, namespace_constant|
    output += [output.last.const_get(namespace_constant)]
  end[1..-1].uniq.reverse
end

.reset_component_namespacesObject



183
184
185
# File 'lib/glimmer/web/component.rb', line 183

def reset_component_namespaces
  @component_namespaces = Set[Object, Glimmer::Web]
end

Instance Method Details

#add_observer(observer, attribute_name) ⇒ Object



271
272
273
274
275
276
277
# File 'lib/glimmer/web/component.rb', line 271

def add_observer(observer, attribute_name)
  if has_instance_method?(attribute_name)
    super(observer, attribute_name)
  else
    @markup_root.add_observer(observer, attribute_name)
  end
end

#attribute_setter(attribute_name) ⇒ Object



308
309
310
# File 'lib/glimmer/web/component.rb', line 308

def attribute_setter(attribute_name)
  "#{attribute_name}="
end

#can_add_observer?(attribute_name) ⇒ Boolean

Returns:

  • (Boolean)


267
268
269
# File 'lib/glimmer/web/component.rb', line 267

def can_add_observer?(attribute_name)
  has_instance_method?(attribute_name) || has_instance_method?("#{attribute_name}?") || @markup_root.can_add_observer?(attribute_name)
end

#can_handle_observation_request?(observation_request) ⇒ Boolean

Returns:

  • (Boolean)


247
248
249
250
251
252
253
254
255
# File 'lib/glimmer/web/component.rb', line 247

def can_handle_observation_request?(observation_request)
  observation_request = observation_request.to_s
  result = false
  if observation_request.start_with?('on_updated_')
    property = observation_request.sub(/^on_updated_/, '')
    result = can_add_observer?(property)
  end
  result || markup_root&.can_handle_observation_request?(observation_request)
end

#content(&block) ⇒ Object

Returns content block if used as an attribute reader (no args) Otherwise, if a block is passed, it adds it as content to this Glimmer web component



319
320
321
322
323
324
325
# File 'lib/glimmer/web/component.rb', line 319

def content(&block)
  if block_given?
    Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Web::ComponentExpression.new, self.class.keyword, &block)
  else
    @content
  end
end

#get_attribute(attribute_name) ⇒ Object



300
301
302
303
304
305
306
# File 'lib/glimmer/web/component.rb', line 300

def get_attribute(attribute_name)
  if has_instance_method?(attribute_name)
    send(attribute_name)
  else
    @markup_root.get_attribute(attribute_name)
  end
end

#handle_observation_request(observation_request, block) ⇒ Object



257
258
259
260
261
262
263
264
265
# File 'lib/glimmer/web/component.rb', line 257

def handle_observation_request(observation_request, block)
  observation_request = observation_request.to_s
  if observation_request.start_with?('on_updated_')
    property = observation_request.sub(/^on_updated_/, '') # TODO look into eliminating duplication from above
    add_observer(DataBinding::Observer.proc(&block), property) if can_add_observer?(property)
  else
    markup_root.handle_observation_request(observation_request, block)
  end
end

#has_attribute?(attribute_name, *args) ⇒ Boolean

Returns:

  • (Boolean)


279
280
281
282
# File 'lib/glimmer/web/component.rb', line 279

def has_attribute?(attribute_name, *args)
  has_instance_method?(attribute_setter(attribute_name)) ||
    @markup_root.has_attribute?(attribute_name, *args)
end

#has_instance_method?(method_name) ⇒ Boolean

This method ensures it has an instance method not coming from Glimmer DSL

Returns:

  • (Boolean)


293
294
295
296
297
298
# File 'lib/glimmer/web/component.rb', line 293

def has_instance_method?(method_name)
  respond_to?(method_name) and
    !markup_root&.respond_to?(method_name) and
    !method(method_name)&.source_location&.first&.include?('glimmer/dsl/engine.rb') and
    !method(method_name)&.source_location&.first&.include?('glimmer/web/element_proxy.rb')
end

#initialize(parent, args, options, &content) ⇒ Object

Raises:

  • (Glimmer::Error)


197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/glimmer/web/component.rb', line 197

def initialize(parent, args, options, &content)
  Component.interpretation_stack.push(self)
  @parent = parent
  options = args.delete_at(-1) if args.is_a?(Array) && args.last.is_a?(Hash)
  if args.is_a?(Hash)
    options = args
    args = []
  end
  options ||= {}
  @args = args
  options ||= {}
  @options = self.class.options.merge(options)
  @content = Util::ProcTracker.new(content) if content
  execute_hooks('before_render')
  markup_block = self.class.instance_variable_get("@markup_block")
  raise Glimmer::Error, 'Invalid Glimmer web component for having no markup! Please define markup block!' if markup_block.nil?
  @markup_root = instance_exec(&markup_block)
  @markup_root.options[:parent] = options[:parent] if !options[:parent].nil?
  @parent ||= @markup_root.parent
  raise Glimmer::Error, 'Invalid Glimmer web component for having an empty markup! Please fill markup block!' if @markup_root.nil?
  if options[:render] != false
    execute_hooks('after_render')
  else
    on_render_listener = proc { execute_hooks('after_render') }
    @markup_root.handle_observation_request('on_render', on_render_listener)
  end
  
  # TODO adapt for web
  observer_registration_cleanup_listener = proc do
    observer_registrations.compact.each(&:deregister)
    observer_registrations.clear
  end
  @markup_root.handle_observation_request('on_remove', observer_registration_cleanup_listener)
  post_add_content if content.nil?
end

#local_respond_to?Object



337
# File 'lib/glimmer/web/component.rb', line 337

alias local_respond_to? respond_to_missing?

#observer_registrationsObject

This stores observe keyword registrations of model/attribute observers



243
244
245
# File 'lib/glimmer/web/component.rb', line 243

def observer_registrations
  @observer_registrations ||= []
end

#post_add_contentObject



238
239
240
# File 'lib/glimmer/web/component.rb', line 238

def post_add_content
  Component.interpretation_stack.pop
end

#post_initialize_child(child) ⇒ Object

Subclasses may override to perform post initialization work on an added child



234
235
236
# File 'lib/glimmer/web/component.rb', line 234

def post_initialize_child(child)
  # No Op by default
end

#render(parent: nil, custom_parent_dom_element: nil, brand_new: false) ⇒ Object



312
313
314
315
# File 'lib/glimmer/web/component.rb', line 312

def render(parent: nil, custom_parent_dom_element: nil, brand_new: false)
  # this method is defined to prevent displaying a harmless Glimmer no keyword error as an annoying useless warning
  @markup_root&.render(parent: parent, custom_parent_dom_element: custom_parent_dom_element, brand_new: brand_new)
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


338
339
340
341
342
# File 'lib/glimmer/web/component.rb', line 338

def respond_to_missing?(method_name, include_private = false)
  super(method_name, include_private) or
    can_handle_observation_request?(method_name) or
    markup_root.respond_to?(method_name, include_private)
end

#set_attribute(attribute_name, *args) ⇒ Object



284
285
286
287
288
289
290
# File 'lib/glimmer/web/component.rb', line 284

def set_attribute(attribute_name, *args)
  if has_instance_method?(attribute_setter(attribute_name))
    send(attribute_setter(attribute_name), *args)
  else
    @markup_root.set_attribute(attribute_name, *args)
  end
end