From b3b0a5ac162ad25bca822f0cb827a907781166ac Mon Sep 17 00:00:00 2001 From: Didier Lafforgue Date: Thu, 22 Mar 2012 00:50:34 +0100 Subject: [PATCH] new editable element for a page: the control --- .../editable_elements/edit_all_view.js.coffee | 4 + .../views/pages/_form_view.js.coffee | 1 + app/models/locomotive/editable_element.rb | 154 +++++++++--------- app/models/locomotive/editable_file.rb | 3 + app/models/locomotive/editable_long_text.rb | 2 + .../extensions/page/editable_elements.rb | 4 +- .../pages/_editable_elements.html.haml | 12 ++ doc/TODO | 13 +- features/public/editable_elements.feature | 14 +- features/step_definitions/more_web_steps.rb | 10 +- lib/locomotive/liquid/tags/editable.rb | 2 +- .../liquid/tags/editable/content.rb | 49 ------ lib/locomotive/liquid/tags/editable/file.rb | 2 - .../liquid/tags/editable/content_spec.rb | 97 ----------- 14 files changed, 132 insertions(+), 235 deletions(-) delete mode 100644 lib/locomotive/liquid/tags/editable/content.rb delete mode 100644 spec/lib/locomotive/liquid/tags/editable/content_spec.rb diff --git a/app/assets/javascripts/locomotive/views/editable_elements/edit_all_view.js.coffee b/app/assets/javascripts/locomotive/views/editable_elements/edit_all_view.js.coffee index a066ba05..ccc50fbb 100644 --- a/app/assets/javascripts/locomotive/views/editable_elements/edit_all_view.js.coffee +++ b/app/assets/javascripts/locomotive/views/editable_elements/edit_all_view.js.coffee @@ -32,6 +32,9 @@ class Locomotive.Views.EditableElements.EditAllView extends Backbone.View view.model = @collection.get(view.model.get('id')) view.refresh() + unbind_model: -> + _.each @_editable_elements_views, (view) => Backbone.ModelBinding.unbind(view) + render_elements: -> index = 0 @@ -45,6 +48,7 @@ class Locomotive.Views.EditableElements.EditAllView extends Backbone.View when 'EditableShortText' then Locomotive.Views.EditableElements.ShortTextView when 'EditableLongText' then Locomotive.Views.EditableElements.LongTextView when 'EditableFile' then Locomotive.Views.EditableElements.FileView + when 'EditableControl' then Locomotive.Views.EditableElements.ControlView view = new view_class(model: element) @$("#block-#{block.index} > fieldset > ol").append(view.render().el) diff --git a/app/assets/javascripts/locomotive/views/pages/_form_view.js.coffee b/app/assets/javascripts/locomotive/views/pages/_form_view.js.coffee index 7682e52d..87a9063a 100644 --- a/app/assets/javascripts/locomotive/views/pages/_form_view.js.coffee +++ b/app/assets/javascripts/locomotive/views/pages/_form_view.js.coffee @@ -90,6 +90,7 @@ class Locomotive.Views.Pages.FormView extends Locomotive.Views.Shared.FormView # Just re-connect the model and the views (+ redraw the file fields) refresh_editable_elements: -> + @editable_elements_view.unbind_model() @editable_elements_view.collection = @model.get('editable_elements') @editable_elements_view.refresh() diff --git a/app/models/locomotive/editable_element.rb b/app/models/locomotive/editable_element.rb index 1cf75b4a..da67dcfd 100644 --- a/app/models/locomotive/editable_element.rb +++ b/app/models/locomotive/editable_element.rb @@ -1,97 +1,101 @@ module Locomotive class EditableElement - include ::Mongoid::Document + include ::Mongoid::Document - ## fields ## - field :slug - field :block - field :hint - field :priority, :type => Integer, :default => 0 - field :fixed, :type => Boolean, :default => false - field :disabled, :type => Boolean, :default => false, :localize => true - field :from_parent, :type => Boolean, :default => false - field :locales, :type => Array, :default => [] + ## fields ## + field :slug + field :block + field :hint + field :priority, :type => Integer, :default => 0 + field :fixed, :type => Boolean, :default => false + field :disabled, :type => Boolean, :default => false, :localize => true + field :from_parent, :type => Boolean, :default => false + field :locales, :type => Array, :default => [] - ## associations ## - embedded_in :page, :class_name => 'Locomotive::Page', :inverse_of => :editable_elements + ## associations ## + embedded_in :page, :class_name => 'Locomotive::Page', :inverse_of => :editable_elements - ## validations ## - validates_presence_of :slug + ## validations ## + validates_presence_of :slug - ## callbacks ## - after_save :propagate_content, :if => :fixed? + ## callbacks ## + after_save :propagate_content, :if => :fixed? - ## scopes ## - scope :by_priority, :order_by => [[:priority, :desc]] + ## scopes ## + scope :by_priority, :order_by => [[:priority, :desc]] - ## methods ## + ## methods ## - def disabled? - !!self.disabled # the original method does not work quite well with the localization - end + def disabled? + !!self.disabled # the original method does not work quite well with the localization + end - # Determines if the current element can be edited in the back-office - # - def editable? - !self.disabled? && self.locales.include?(::Mongoid::Fields::I18n.locale.to_s) && (!self.fixed? || !self.from_parent?) - end + # Determines if the current element can be edited in the back-office + # + def editable? + !self.disabled? && self.locales.include?(::Mongoid::Fields::I18n.locale.to_s) && (!self.fixed? || !self.from_parent?) + end - def _run_rearrange_callbacks - # callback from page/tree. not needed in the editable elements - end + def _run_rearrange_callbacks + # callback from page/tree. not needed in the editable elements + end - def default_content? - # needs to be overridden for each kind of elements - true - end + def default_content? + # needs to be overridden for each kind of elements + true + end - # Copy attributes extracted from the corresponding Liquid tag - # Each editable element overrides this method. - # - # @param [ Hash ] attributes The up-to-date attributes - # - def copy_attributes(attributes) - self.attributes = attributes - end + # Copy attributes extracted from the corresponding Liquid tag + # Each editable element overrides this method. + # + # @param [ Hash ] attributes The up-to-date attributes + # + def copy_attributes(attributes) + self.attributes = attributes + end - # Copy attributes from an existing editable element coming - # from the parent page. Each editable element may or not - # override this method. The source element is a new record. - # - # @param [ EditableElement] el The source element - # - def copy_attributes_from(el) - self.attributes = el.attributes.reject { |attr| !%w(slug block hint priority fixed disabled locales from_parent).include?(attr) } - self.from_parent = true - end + # Copy attributes from an existing editable element coming + # from the parent page. Each editable element may or not + # override this method. The source element is a new record. + # + # @param [ EditableElement] el The source element + # + def copy_attributes_from(el) + self.attributes = el.attributes.reject { |attr| !%w(slug block hint priority fixed disabled locales from_parent).include?(attr) } + self.from_parent = true + end - # Make sure the current locale is added to the list - # of locales for the current element so that we know - # in which languages the element was translated. - # - def add_current_locale - locale = ::Mongoid::Fields::I18n.locale.to_s - self.locales << locale unless self.locales.include?(locale) - end + # Make sure the current locale is added to the list + # of locales for the current element so that we know + # in which languages the element was translated. + # + def add_current_locale + locale = ::Mongoid::Fields::I18n.locale.to_s + self.locales << locale unless self.locales.include?(locale) + end - protected + protected - def _selector - locale = ::Mongoid::Fields::I18n.locale - { - 'site_id' => self.page.site_id, - "template_dependencies.#{locale}" => { '$in' => [self.page._id] }, - 'editable_elements.fixed' => true, - 'editable_elements.block' => self.block, - 'editable_elements.slug' => self.slug - } - end + def _selector + locale = ::Mongoid::Fields::I18n.locale + { + 'site_id' => self.page.site_id, + "template_dependencies.#{locale}" => { '$in' => [self.page._id] }, + 'editable_elements.fixed' => true, + 'editable_elements.block' => self.block, + 'editable_elements.slug' => self.slug + } + end - def propagate_content - # needs to be overridden for each kind of elements (file, short text, ...etc) - true - end + # Update the value (or content) of the elements matching the same block/slug + # as the current element in all the pages inheriting from the current page. + # This method is called only if the element has the "fixed" property set to true. + # It also needs to be overridden for each kind of elements (file, short text, ...etc) + # + def propagate_content + true + end end end \ No newline at end of file diff --git a/app/models/locomotive/editable_file.rb b/app/models/locomotive/editable_file.rb index 496a53cf..70e6cf36 100644 --- a/app/models/locomotive/editable_file.rb +++ b/app/models/locomotive/editable_file.rb @@ -1,12 +1,15 @@ module Locomotive class EditableFile < EditableElement + ## behaviours ## mount_uploader 'source', EditableFileUploader replace_field 'source', ::String, true + ## fields ## field :default_source_url, :localize => true + ## callbacks ## after_save :propagate_content ## methods ## diff --git a/app/models/locomotive/editable_long_text.rb b/app/models/locomotive/editable_long_text.rb index 44f45b28..c9d2467e 100644 --- a/app/models/locomotive/editable_long_text.rb +++ b/app/models/locomotive/editable_long_text.rb @@ -1,6 +1,8 @@ module Locomotive class EditableLongText < EditableShortText + ## methods ## + def as_json(options = {}) Locomotive::EditableLongTextPresenter.new(self).as_json end diff --git a/app/models/locomotive/extensions/page/editable_elements.rb b/app/models/locomotive/extensions/page/editable_elements.rb index da3ce22a..31fc0d8f 100644 --- a/app/models/locomotive/extensions/page/editable_elements.rb +++ b/app/models/locomotive/extensions/page/editable_elements.rb @@ -26,7 +26,7 @@ module Locomotive end def enabled_editable_elements - self.editable_elements.by_priority.find_all(&:editable?) # { |el| !el.editable? } + self.editable_elements.by_priority.find_all(&:editable?) end def editable_elements_grouped_by_blocks @@ -70,7 +70,7 @@ module Locomotive new_el = self.editable_elements.build({}, el.class) new_el.copy_attributes_from(el) else - existing_el.disabled = false # = { :disabled => false } + existing_el.disabled = false end end end diff --git a/app/views/locomotive/pages/_editable_elements.html.haml b/app/views/locomotive/pages/_editable_elements.html.haml index e403d8c7..640c40c9 100644 --- a/app/views/locomotive/pages/_editable_elements.html.haml +++ b/app/views/locomotive/pages/_editable_elements.html.haml @@ -49,4 +49,16 @@ %p.inline-hints {{hint}} {{/if}} + = hidden_field_tag 'page[editable_elements_attributes][{{index}}][id]', '{{id}}', :id => 'page_editable_elements_attributes_{{index}}_id' + +%script{ :type => 'text/html', :id => 'editable_control_input' } + + %label{ :for => 'page_editable_elements_attributes_{{index}}_content' } {{label}} + + = select_tag 'page[editable_elements_attributes][{{index}}][content]', raw('{{#each options}}{{/each}}'), :id => 'page_editable_elements_attributes_{{index}}_content', :class => 'content' + + {{#if hint}} + %p.inline-hints {{hint}} + {{/if}} + = hidden_field_tag 'page[editable_elements_attributes][{{index}}][id]', '{{id}}', :id => 'page_editable_elements_attributes_{{index}}_id' \ No newline at end of file diff --git a/doc/TODO b/doc/TODO index 36e251f7..f82abc85 100644 --- a/doc/TODO +++ b/doc/TODO @@ -20,7 +20,7 @@ x edit my site x domains x roles x save -- fix other sections +x fix other sections x edit my account x create a new site x create a new accounts @@ -32,7 +32,7 @@ x edit my site x import/export x export x site picker - - content types + x content types x move content instances into their own collection x manage custom_fields x automatic name @@ -65,7 +65,7 @@ x edit my site x bug text formatting x custom_fields: use the appropriate icon to drag select options x bug ui with contents popup - - use list_or_group_entries instead of ordered_entries + x use list_or_group_entries instead of ordered_entries x i18n x add locales a site responds to x locale switcher @@ -90,7 +90,7 @@ x i18n x deployment x fix integration problems x pre-compile assets -- API +x API x authentication from a token + controller to deliver a token x api routes x change api location @@ -115,6 +115,8 @@ x heroku module for locomotive x remove the import / export scripts x remove the cross domain authentication (use auth_token instead) - where to put Locomotive::InlineEditorMiddleware ? +x global regions: keyword in editable element (http://www.mongodb.org/display/DOCS/Updating) +x override sort for contents - bugs / ui tweaks x unable to toggle the "required" check_boxes for content types @@ -139,7 +141,6 @@ BACKLOG: - html view in the aloha popup - editable elements should wrap a tag: div, h1, ...etc (default span) - edit images (upload new ones, ...etc) => wait for aloha or send them an email ? -- global regions: keyword in editable element (http://www.mongodb.org/display/DOCS/Updating) - cucumber features for admin pages (in progress) (- duostack/doutcloud version) - write my first tutorial about locomotive @@ -151,7 +152,7 @@ BACKLOG: - sync data - import only theme assets - endless pagination -- override sort for contents + - tooltip to explain the difference between 1.) Admin 2.) Author 3.) Designer? - [bushido] guiders / welcome page / devise cas authentication (SSO) diff --git a/features/public/editable_elements.feature b/features/public/editable_elements.feature index 053d1bea..e7359e0c 100644 --- a/features/public/editable_elements.feature +++ b/features/public/editable_elements.feature @@ -125,7 +125,7 @@ Scenario: Insert editable files """ My application file is /default.pdf """ - + Scenario: Render liquid variable used as default editable file name Given a page named "hello-world" with the template: """ @@ -135,7 +135,17 @@ Scenario: Render liquid variable used as default editable file name When I view the rendered page at "/hello-world" Then the rendered output should look like: """ - + My application file is /different-default.pdf """ +Scenario: Simple select element + Given a page named "hello-world" with the template: + """ + {% block menuecontent %}{% editable_control 'menueposition', options: 'top=Top of the Page,bottom=Bottom of the Page' %}bottom{% endeditable_control %}{% endblock %} + """ + When I view the rendered page at "/hello-world" + Then the rendered output should look like: + """ + bottom + """ \ No newline at end of file diff --git a/features/step_definitions/more_web_steps.rb b/features/step_definitions/more_web_steps.rb index eba8b90c..b1ea9e87 100644 --- a/features/step_definitions/more_web_steps.rb +++ b/features/step_definitions/more_web_steps.rb @@ -1,5 +1,5 @@ When /^I follow image link "([^"]*)"$/ do |img_alt| - find(:xpath, "//img[@alt = '#{img_alt}']/parent::a").click() + find(:xpath, "//img[@alt = '#{img_alt}']/parent::a").click() end Then /^I should get a download with the filename "([^\"]*)"$/ do |filename| @@ -31,3 +31,11 @@ Then /^"([^"]*)" should( not)? be an option for "([^"]*)"(?: within "([^\"]*)")? field_labeled(field).first(:xpath, ".//option[text() = '#{value}']").send(expectation, be_present) end end + +Then /^"([^"]*)" should be selected for "([^"]*)"$/ do |value, field| + assert page.has_xpath?("//select[@name='#{field}' and option[@selected and contains(text(), '#{value}')]]") +end + +When /^I reload the page$/ do + visit current_path +end \ No newline at end of file diff --git a/lib/locomotive/liquid/tags/editable.rb b/lib/locomotive/liquid/tags/editable.rb index ed857c94..044afc8e 100644 --- a/lib/locomotive/liquid/tags/editable.rb +++ b/lib/locomotive/liquid/tags/editable.rb @@ -2,4 +2,4 @@ require 'locomotive/liquid/tags/editable/base' require 'locomotive/liquid/tags/editable/short_text' require 'locomotive/liquid/tags/editable/long_text' require 'locomotive/liquid/tags/editable/file' -require 'locomotive/liquid/tags/editable/content' \ No newline at end of file +require 'locomotive/liquid/tags/editable/control' \ No newline at end of file diff --git a/lib/locomotive/liquid/tags/editable/content.rb b/lib/locomotive/liquid/tags/editable/content.rb deleted file mode 100644 index 280d6a9a..00000000 --- a/lib/locomotive/liquid/tags/editable/content.rb +++ /dev/null @@ -1,49 +0,0 @@ -module Locomotive - module Liquid - module Tags - module Editable - class Content < ::Liquid::Tag - - Syntax = /(#{::Liquid::Expression}+)?/ - - def initialize(tag_name, markup, tokens, context) - if markup =~ Syntax - @slug = $1 - @options = { :inherit => false } - markup.scan(::Liquid::TagAttributes) { |key, value| @options[key.to_sym] = value.gsub(/"|'/, '') } - else - raise ::Liquid::SyntaxError.new("Syntax Error in 'content' - Valid syntax: slug") - end - - super - end - - def render(context) - page = context.registers[:page] - element = find_element(page) - - if element.nil? && @options[:inherit] != false - while page.parent.present? && element.nil? - page = page.parent - element = find_element(page) - end - end - - if element.present? - return element.content - else - raise ::Liquid::SyntaxError.new("Error in 'content' - Can't find editable element called `#{@slug}`") - end - end - - def find_element(page) - page.editable_elements.where(:slug => @slug).first - end - - end - - ::Liquid::Template.register_tag('content', Content) - end - end - end -end \ No newline at end of file diff --git a/lib/locomotive/liquid/tags/editable/file.rb b/lib/locomotive/liquid/tags/editable/file.rb index 71a0e47d..1f0d5677 100644 --- a/lib/locomotive/liquid/tags/editable/file.rb +++ b/lib/locomotive/liquid/tags/editable/file.rb @@ -6,8 +6,6 @@ module Locomotive protected - protected - def default_element_attributes if @nodelist.first.is_a?(String) super.merge(:default_source_url => @nodelist.first.try(:to_s)) diff --git a/spec/lib/locomotive/liquid/tags/editable/content_spec.rb b/spec/lib/locomotive/liquid/tags/editable/content_spec.rb deleted file mode 100644 index 5a78da65..00000000 --- a/spec/lib/locomotive/liquid/tags/editable/content_spec.rb +++ /dev/null @@ -1,97 +0,0 @@ -require 'spec_helper' - -describe Locomotive::Liquid::Tags::Editable::Content do - - before :each do - Locomotive::Liquid::Tags::Editable::Content.any_instance.stubs(:end_tag).returns(true) - end - - context 'syntax' do - - it 'should have a valid syntax' do - ["slug", "slug, inherit: true"].each do |markup| - lambda do - Locomotive::Liquid::Tags::Editable::Content.new('content', markup, ["{% content %}"], {}) - end.should_not raise_error - end - end - - end - - context 'output' do - - before :each do - Locomotive::EditableElement.any_instance.stubs(:content).returns("test string") - end - - context 'inheriting from a parent' do - - before :each do - @parent = FactoryGirl.build(:page) - @child = FactoryGirl.build(:page) - - @child.stubs(:parent).returns(@parent) - end - - it 'should return the parents field if inherit is set' do - @element = @parent.editable_elements.create(:slug => 'test') - @child.stubs(:raw_template).returns("{% content test, inherit: true %}") - template = Liquid::Template.parse(@child.raw_template) - text = template.render!(liquid_context(:page => @child)) - text.should match /test string/ - end - - it 'should raise an exception if it cant find the field' do - @child.stubs(:raw_template).returns("{% content test, inherit: true %}") - template = Liquid::Template.parse(@child.raw_template) - lambda do - template.render!(liquid_context(:page => @child)) - end.should raise_error - end - - after :each do - @parent.editable_elements.destroy_all - end - - end - - context 'reading from the same page' do - - before :each do - @page = FactoryGirl.build(:page) - end - - it 'should return the previously defined field' do - @element = @page.editable_elements.create(:slug => 'test') - @page.stubs(:raw_template).returns("{% content test %}") - template = Liquid::Template.parse(@page.raw_template) - text = template.render!(liquid_context(:page => @page)) - text.should match /test string/ - end - - it 'should raise an exception if it wasnt defined' do - @page.stubs(:raw_template).returns("{% content test %}") - template = Liquid::Template.parse(@page.raw_template) - lambda do - template.render!(liquid_context(:page => @page)) - end.should raise_error - end - - after :each do - @page.editable_elements.destroy_all - end - - end - - end - - # ___ helpers methods ___ # - - def liquid_context(options = {}) - ::Liquid::Context.new({}, {}, - { - :page => options[:page] - }, true) - end - -end