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 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 {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 {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 {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 # Clones the {BasicWidget Widget}. The clone gets all # attributes of the original widget except nested widget attributes. # The clone is not attached to an {BasicObj Obj} initially. # It only becomes usable by assigning it to a widget attribute of an Obj. # # @example # # From another_obj, take the first widget in my_widgets, # # and put a clone in obj's my_widgets. # obj.update(my_widgets: [another_obj.my_widgets.first.clone]) # # @api public def clone attributes = {} data_from_cms.all_custom_attributes.each do |attr_name| if type_of_attribute(attr_name) != 'widget' attributes[attr_name] = read_attribute(attr_name) end end self.class.new(attributes) end def id if @id @id else raise_not_persisted_error end end def persisted? @attributes_to_be_saved.nil? end def obj_class_name data_from_cms.value_of('_obj_class') end def obj_class raise ScrivitoError, "BasicWidget#obj_class is no longer available"+ ", please use BasicWidget#obj_class_name instead." 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 content changes of this widget. Widget attributes of this widget are not effected. # After calling this method it's as if this widget has been never modified in the current working copy. # This method does not work with +new+ or +deleted+ widgets. # This method also does also not work for the +published+ workspace or the +rtc+ working copy. def revert Workspace.current.assert_revertable case modification when Modification::UNMODIFIED # do nothing when Modification::EDITED previous_obj_content = CmsRestApi.get("revisions/#{Workspace.current.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.current.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 ({BasicObj} or {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 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_attribtues @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 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 end end