module Scrivito # The CMS widget class # @api public class BasicWidget include AttributeContent def self.type_computer @_type_computer ||= TypeComputer.new(Scrivito::BasicWidget, ::Widget) end def self.reset_type_computer! @_type_computer = nil end def self.hide_from_widget_class_selection? false end attr_accessor :container, :container_field_name attr_writer :obj, :id attr_reader :attributes_to_be_saved delegate :widget_from_pool, to: :obj # Create a new Widget. # The new Widget must be stored inside a container (i.e. an Obj or another Widget) # before it can be used. # # See {Scrivito::BasicObj.create Obj.create} for a detailed overview of how to set attributes. # @api public # @param [Hash] attributes # # @example Create a widget using a subclass # # you can create widgets by explicitly providing the attribute `_obj_class` # new_widget = Widget.new(_obj_class: "MyWidget") # # but it is better to simply use the constructor of a subclass # new_widget = MyWidget.new # # @example Create a widget and store it inside an Obj # # create the widget # new_widget = Widget.new(_obj_class: "MyWidget") # # store it inside an obj # my_obj(my_widget_field: [new_widget]) # def initialize(attributes = {}) @attributes_to_be_saved = self.class.with_default_obj_class(attributes) end def revision obj.revision end # Update the attributes of this Widget # # See {Scrivito::BasicObj.create Obj.create} for a detailed overview of how to set attributes # @api public # @param [Hash] attributes def update(attributes) obj.update(_widget_pool: { self => attributes }) reload end # Destroys the {Scrivito::BasicWidget Widget} in the current {Workspace} # @api public def destroy new_widget_list = container[container_field_name] - [self] container.update(container_field_name => new_widget_list) end # # Create a copy of a {Scrivito::BasicWidget Widget}. # # @api public # # The copy will have all attributes of the original widget including its widgets. # Its attributes can be accessed only after it has been stored in a widget field of an # {Scrivito::BasicObj Obj}, since initially the copy is not stored in any widget field. # # @example Duplicate the first widget in field +my_widget+ # obj.update(my_widgets: obj.my_widgets.push(obj.my_widgets.first.copy)) # def copy attrs = {} each_custom_attribute do |attr_name, attr_value, attr_type| attrs[attr_name] = attr_type == 'widget' ? attr_value.map(&:copy) : attr_value end self.class.new(attrs) end def copy_for_restore(referenced_ids) attrs = {} each_custom_attribute do |attr_name, attr_value, attr_type| attrs[attr_name] = if attr_type == 'widget' attr_value.reject { |widget| referenced_ids.include?(widget.id) } .map { |widget| widget.copy_for_restore(referenced_ids) } else attr_value end end attrs['_id'] = id self.class.new(attrs) end def clone raise "The method `clone' was removed. Please use `copy' instead" end def id if @id @id else raise_not_persisted_error end end def persisted? @attributes_to_be_saved.nil? end def ==(other) other.respond_to?(:obj) && obj == other.obj && other.respond_to?(:id) && id == other.id end def eql?(other) self == other end # # Reverts all changes made to the +Widget+ in the current workspace. # # @api public # # @note This method does not support +Widget+s, which are +new+. # Please use {Scrivito::BasicWidget#destroy Widget#destroy} to destroy them. # @note This method does not support +Widget+s, which are +deleted+. # Please use {Scrivito::BasicObj#revert Obj#revert} to restore them. # # @raise [ScrivitoError] If the current workspace is +published+. # @raise [ScrivitoError] If the current workspace is the +rtc+ workspace. # @raise [ScrivitoError] If the +Widget+ is +new+. # @raise [ScrivitoError] If the +Widget+ is +deleted+. # def revert workspace.assert_revertable case modification when Modification::UNMODIFIED # do nothing when Modification::EDITED previous_obj_content = CmsRestApi.get("revisions/#{workspace.base_revision_id}/objs/#{obj.id}") previous_widget_content = previous_obj_content["_widget_pool"]["#{id}"] previous_widget_content.delete_if do |attribute_name, _| type_of_attribute(attribute_name) == "widget" end CmsRestApi.put("workspaces/#{workspace.id}/objs/#{obj.id}", { obj: {_widget_pool: {id => previous_widget_content}} }) else raise ScrivitoError, "cannot revert changes, since widget is #{modification}." end end def in_revision(revision) obj_in_revision = obj.in_revision(revision) obj_in_revision && obj_in_revision.widget_from_pool(id) end def new?(revision=Workspace.current.base_revision) return false unless revision obj.new?(revision) || cms_data_for_revision(revision).nil? end def deleted?(revision=Workspace.current.base_revision) return false unless revision obj.deleted?(revision) end def modification(revision=Workspace.current.base_revision) return Modification::UNMODIFIED unless revision if deleted?(revision) Modification::DELETED elsif new?(revision) Modification::NEW else if data_from_cms == cms_data_for_revision(revision) Modification::UNMODIFIED else Modification::EDITED end end end def hash if @obj && @id (id + obj.id).hash else super end end # returns the entity ({Scrivito::BasicObj} or {Scrivito::BasicWidget}) that references this widget # @api public def container @container || cache_container_and_field_name_for_widget.first end # returns the name of the widget field that references this widget # @api public def container_field_name @container_field_name || cache_container_and_field_name_for_widget.second end def container_field_index container[container_field_name].index(self) end def has_attribute?(key) key.to_s == '_obj_class' || super end def inspect if @id if @obj "<#{self.class} id=\"#{id}\" obj.id=\"#{obj.id}\">" else "<#{self.class} id=\"#{id}\">" end else "<#{self.class}>" end end def obj if @obj @obj else raise_not_persisted_error end end def forget_unsaved_attributes @attributes_to_be_saved = nil reload end def reload obj.reload update_proc = -> { obj.widget_data_from_pool(id) } update_data(update_proc) end # This method determines the description that is shown in the widget tooltips. # It can be overriden by a custom value. # @api public def description_for_editor obj_class_name end private def workspace if revision.workspace revision.workspace else raise ScrivitoError, "No workspace set for the obj of this widget" end end def data_from_cms if persisted? super else raise_not_persisted_error end end def raise_not_persisted_error raise ScrivitoError.new('Can not access a new widget before it has been saved') end def cms_data_for_revision(revision) obj.widget_data_for_revision(id, revision) end def cache_container_and_field_name_for_widget @cache_container_and_field_name_for_widget ||= obj.container_and_field_name_for_widget(id) end def each_custom_attribute data_from_cms.all_custom_attributes.each do |attr_name| yield attr_name, read_attribute(attr_name), type_of_attribute(attr_name) end end end end