new editable element for a page: the control

This commit is contained in:
Didier Lafforgue 2012-03-22 00:50:34 +01:00
parent ab5a4755b8
commit b3b0a5ac16
14 changed files with 132 additions and 235 deletions

View File

@ -32,6 +32,9 @@ class Locomotive.Views.EditableElements.EditAllView extends Backbone.View
view.model = @collection.get(view.model.get('id')) view.model = @collection.get(view.model.get('id'))
view.refresh() view.refresh()
unbind_model: ->
_.each @_editable_elements_views, (view) => Backbone.ModelBinding.unbind(view)
render_elements: -> render_elements: ->
index = 0 index = 0
@ -45,6 +48,7 @@ class Locomotive.Views.EditableElements.EditAllView extends Backbone.View
when 'EditableShortText' then Locomotive.Views.EditableElements.ShortTextView when 'EditableShortText' then Locomotive.Views.EditableElements.ShortTextView
when 'EditableLongText' then Locomotive.Views.EditableElements.LongTextView when 'EditableLongText' then Locomotive.Views.EditableElements.LongTextView
when 'EditableFile' then Locomotive.Views.EditableElements.FileView when 'EditableFile' then Locomotive.Views.EditableElements.FileView
when 'EditableControl' then Locomotive.Views.EditableElements.ControlView
view = new view_class(model: element) view = new view_class(model: element)
@$("#block-#{block.index} > fieldset > ol").append(view.render().el) @$("#block-#{block.index} > fieldset > ol").append(view.render().el)

View File

@ -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) # Just re-connect the model and the views (+ redraw the file fields)
refresh_editable_elements: -> refresh_editable_elements: ->
@editable_elements_view.unbind_model()
@editable_elements_view.collection = @model.get('editable_elements') @editable_elements_view.collection = @model.get('editable_elements')
@editable_elements_view.refresh() @editable_elements_view.refresh()

View File

@ -1,97 +1,101 @@
module Locomotive module Locomotive
class EditableElement class EditableElement
include ::Mongoid::Document include ::Mongoid::Document
## fields ## ## fields ##
field :slug field :slug
field :block field :block
field :hint field :hint
field :priority, :type => Integer, :default => 0 field :priority, :type => Integer, :default => 0
field :fixed, :type => Boolean, :default => false field :fixed, :type => Boolean, :default => false
field :disabled, :type => Boolean, :default => false, :localize => true field :disabled, :type => Boolean, :default => false, :localize => true
field :from_parent, :type => Boolean, :default => false field :from_parent, :type => Boolean, :default => false
field :locales, :type => Array, :default => [] field :locales, :type => Array, :default => []
## associations ## ## associations ##
embedded_in :page, :class_name => 'Locomotive::Page', :inverse_of => :editable_elements embedded_in :page, :class_name => 'Locomotive::Page', :inverse_of => :editable_elements
## validations ## ## validations ##
validates_presence_of :slug validates_presence_of :slug
## callbacks ## ## callbacks ##
after_save :propagate_content, :if => :fixed? after_save :propagate_content, :if => :fixed?
## scopes ## ## scopes ##
scope :by_priority, :order_by => [[:priority, :desc]] scope :by_priority, :order_by => [[:priority, :desc]]
## methods ## ## methods ##
def disabled? def disabled?
!!self.disabled # the original method does not work quite well with the localization !!self.disabled # the original method does not work quite well with the localization
end end
# Determines if the current element can be edited in the back-office # Determines if the current element can be edited in the back-office
# #
def editable? def editable?
!self.disabled? && self.locales.include?(::Mongoid::Fields::I18n.locale.to_s) && (!self.fixed? || !self.from_parent?) !self.disabled? && self.locales.include?(::Mongoid::Fields::I18n.locale.to_s) && (!self.fixed? || !self.from_parent?)
end end
def _run_rearrange_callbacks def _run_rearrange_callbacks
# callback from page/tree. not needed in the editable elements # callback from page/tree. not needed in the editable elements
end end
def default_content? def default_content?
# needs to be overridden for each kind of elements # needs to be overridden for each kind of elements
true true
end end
# Copy attributes extracted from the corresponding Liquid tag # Copy attributes extracted from the corresponding Liquid tag
# Each editable element overrides this method. # Each editable element overrides this method.
# #
# @param [ Hash ] attributes The up-to-date attributes # @param [ Hash ] attributes The up-to-date attributes
# #
def copy_attributes(attributes) def copy_attributes(attributes)
self.attributes = attributes self.attributes = attributes
end end
# Copy attributes from an existing editable element coming # Copy attributes from an existing editable element coming
# from the parent page. Each editable element may or not # from the parent page. Each editable element may or not
# override this method. The source element is a new record. # override this method. The source element is a new record.
# #
# @param [ EditableElement] el The source element # @param [ EditableElement] el The source element
# #
def copy_attributes_from(el) def copy_attributes_from(el)
self.attributes = el.attributes.reject { |attr| !%w(slug block hint priority fixed disabled locales from_parent).include?(attr) } self.attributes = el.attributes.reject { |attr| !%w(slug block hint priority fixed disabled locales from_parent).include?(attr) }
self.from_parent = true self.from_parent = true
end end
# Make sure the current locale is added to the list # Make sure the current locale is added to the list
# of locales for the current element so that we know # of locales for the current element so that we know
# in which languages the element was translated. # in which languages the element was translated.
# #
def add_current_locale def add_current_locale
locale = ::Mongoid::Fields::I18n.locale.to_s locale = ::Mongoid::Fields::I18n.locale.to_s
self.locales << locale unless self.locales.include?(locale) self.locales << locale unless self.locales.include?(locale)
end end
protected protected
def _selector def _selector
locale = ::Mongoid::Fields::I18n.locale locale = ::Mongoid::Fields::I18n.locale
{ {
'site_id' => self.page.site_id, 'site_id' => self.page.site_id,
"template_dependencies.#{locale}" => { '$in' => [self.page._id] }, "template_dependencies.#{locale}" => { '$in' => [self.page._id] },
'editable_elements.fixed' => true, 'editable_elements.fixed' => true,
'editable_elements.block' => self.block, 'editable_elements.block' => self.block,
'editable_elements.slug' => self.slug 'editable_elements.slug' => self.slug
} }
end end
def propagate_content # Update the value (or content) of the elements matching the same block/slug
# needs to be overridden for each kind of elements (file, short text, ...etc) # as the current element in all the pages inheriting from the current page.
true # This method is called only if the element has the "fixed" property set to true.
end # It also needs to be overridden for each kind of elements (file, short text, ...etc)
#
def propagate_content
true
end
end end
end end

View File

@ -1,12 +1,15 @@
module Locomotive module Locomotive
class EditableFile < EditableElement class EditableFile < EditableElement
## behaviours ##
mount_uploader 'source', EditableFileUploader mount_uploader 'source', EditableFileUploader
replace_field 'source', ::String, true replace_field 'source', ::String, true
## fields ##
field :default_source_url, :localize => true field :default_source_url, :localize => true
## callbacks ##
after_save :propagate_content after_save :propagate_content
## methods ## ## methods ##

View File

@ -1,6 +1,8 @@
module Locomotive module Locomotive
class EditableLongText < EditableShortText class EditableLongText < EditableShortText
## methods ##
def as_json(options = {}) def as_json(options = {})
Locomotive::EditableLongTextPresenter.new(self).as_json Locomotive::EditableLongTextPresenter.new(self).as_json
end end

View File

@ -26,7 +26,7 @@ module Locomotive
end end
def enabled_editable_elements def enabled_editable_elements
self.editable_elements.by_priority.find_all(&:editable?) # { |el| !el.editable? } self.editable_elements.by_priority.find_all(&:editable?)
end end
def editable_elements_grouped_by_blocks def editable_elements_grouped_by_blocks
@ -70,7 +70,7 @@ module Locomotive
new_el = self.editable_elements.build({}, el.class) new_el = self.editable_elements.build({}, el.class)
new_el.copy_attributes_from(el) new_el.copy_attributes_from(el)
else else
existing_el.disabled = false # = { :disabled => false } existing_el.disabled = false
end end
end end
end end

View File

@ -49,4 +49,16 @@
%p.inline-hints {{hint}} %p.inline-hints {{hint}}
{{/if}} {{/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}}<option value="{{this.value}}" {{#if this.selected}}selected="selected"{{/if}}>{{this.text}}</option>{{/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' = hidden_field_tag 'page[editable_elements_attributes][{{index}}][id]', '{{id}}', :id => 'page_editable_elements_attributes_{{index}}_id'

View File

@ -20,7 +20,7 @@ x edit my site
x domains x domains
x roles x roles
x save x save
- fix other sections x fix other sections
x edit my account x edit my account
x create a new site x create a new site
x create a new accounts x create a new accounts
@ -32,7 +32,7 @@ x edit my site
x import/export x import/export
x export x export
x site picker x site picker
- content types x content types
x move content instances into their own collection x move content instances into their own collection
x manage custom_fields x manage custom_fields
x automatic name x automatic name
@ -65,7 +65,7 @@ x edit my site
x bug text formatting x bug text formatting
x custom_fields: use the appropriate icon to drag select options x custom_fields: use the appropriate icon to drag select options
x bug ui with contents popup 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 i18n
x add locales a site responds to x add locales a site responds to
x locale switcher x locale switcher
@ -90,7 +90,7 @@ x i18n
x deployment x deployment
x fix integration problems x fix integration problems
x pre-compile assets x pre-compile assets
- API x API
x authentication from a token + controller to deliver a token x authentication from a token + controller to deliver a token
x api routes x api routes
x change api location x change api location
@ -115,6 +115,8 @@ x heroku module for locomotive
x remove the import / export scripts x remove the import / export scripts
x remove the cross domain authentication (use auth_token instead) x remove the cross domain authentication (use auth_token instead)
- where to put Locomotive::InlineEditorMiddleware ? - 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 - bugs / ui tweaks
x unable to toggle the "required" check_boxes for content types x unable to toggle the "required" check_boxes for content types
@ -139,7 +141,6 @@ BACKLOG:
- html view in the aloha popup - html view in the aloha popup
- editable elements should wrap a tag: div, h1, ...etc (default span) - 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 ? - 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) - cucumber features for admin pages (in progress)
(- duostack/doutcloud version) (- duostack/doutcloud version)
- write my first tutorial about locomotive - write my first tutorial about locomotive
@ -151,7 +152,7 @@ BACKLOG:
- sync data - sync data
- import only theme assets - import only theme assets
- endless pagination - endless pagination
- override sort for contents
- tooltip to explain the difference between 1.) Admin 2.) Author 3.) Designer? - tooltip to explain the difference between 1.) Admin 2.) Author 3.) Designer?
- [bushido] guiders / welcome page / devise cas authentication (SSO) - [bushido] guiders / welcome page / devise cas authentication (SSO)

View File

@ -125,7 +125,7 @@ Scenario: Insert editable files
""" """
My application file is /default.pdf My application file is /default.pdf
""" """
Scenario: Render liquid variable used as default editable file name Scenario: Render liquid variable used as default editable file name
Given a page named "hello-world" with the template: 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" When I view the rendered page at "/hello-world"
Then the rendered output should look like: Then the rendered output should look like:
""" """
My application file is /different-default.pdf 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
"""

View File

@ -1,5 +1,5 @@
When /^I follow image link "([^"]*)"$/ do |img_alt| 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 end
Then /^I should get a download with the filename "([^\"]*)"$/ do |filename| 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) field_labeled(field).first(:xpath, ".//option[text() = '#{value}']").send(expectation, be_present)
end end
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

View File

@ -2,4 +2,4 @@ require 'locomotive/liquid/tags/editable/base'
require 'locomotive/liquid/tags/editable/short_text' require 'locomotive/liquid/tags/editable/short_text'
require 'locomotive/liquid/tags/editable/long_text' require 'locomotive/liquid/tags/editable/long_text'
require 'locomotive/liquid/tags/editable/file' require 'locomotive/liquid/tags/editable/file'
require 'locomotive/liquid/tags/editable/content' require 'locomotive/liquid/tags/editable/control'

View File

@ -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

View File

@ -6,8 +6,6 @@ module Locomotive
protected protected
protected
def default_element_attributes def default_element_attributes
if @nodelist.first.is_a?(String) if @nodelist.first.is_a?(String)
super.merge(:default_source_url => @nodelist.first.try(:to_s)) super.merge(:default_source_url => @nodelist.first.try(:to_s))

View File

@ -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