Add the reverse has_many field feature (mainly based on pull request #142)
This commit is contained in:
parent
333934c022
commit
67d7b13f4f
2
Gemfile
2
Gemfile
@ -26,7 +26,7 @@ gem 'carrierwave', '~> 0.5.5'
|
||||
gem 'dragonfly', '~> 0.9.1'
|
||||
gem 'rack-cache', :require => 'rack/cache'
|
||||
|
||||
gem 'custom_fields', '1.0.0.beta.21'
|
||||
gem 'custom_fields', '1.0.0.beta.22'
|
||||
gem 'cancan'
|
||||
gem 'fog', '0.8.2'
|
||||
gem 'mimetype-fu'
|
||||
|
@ -87,7 +87,7 @@ GEM
|
||||
capybara (>= 1.0.0)
|
||||
cucumber (~> 1.0.0)
|
||||
nokogiri (>= 1.4.6)
|
||||
custom_fields (1.0.0.beta.21)
|
||||
custom_fields (1.0.0.beta.22)
|
||||
activesupport (~> 3.0.9)
|
||||
mongoid (= 2.0.2)
|
||||
daemons (1.1.4)
|
||||
@ -296,7 +296,7 @@ DEPENDENCIES
|
||||
carrierwave (~> 0.5.5)
|
||||
cells
|
||||
cucumber-rails (= 1.0.2)
|
||||
custom_fields (= 1.0.0.beta.21)
|
||||
custom_fields (= 1.0.0.beta.22)
|
||||
database_cleaner
|
||||
delayed_job (= 2.1.4)
|
||||
delayed_job_mongoid (= 1.0.2)
|
||||
|
@ -11,16 +11,26 @@ module Admin
|
||||
|
||||
before_filter :authorize_content
|
||||
|
||||
helper_method :breadcrumb_root, :breadcrumb_url, :back_url
|
||||
|
||||
def index
|
||||
@contents = @content_type.list_or_group_contents
|
||||
end
|
||||
|
||||
def new
|
||||
new! { @content.attributes = params[:content] }
|
||||
end
|
||||
|
||||
def create
|
||||
create! { edit_admin_content_url(@content_type.slug, @content.id) }
|
||||
create! { after_create_or_update_url }
|
||||
end
|
||||
|
||||
def edit
|
||||
edit! { @content.attributes = params[:content] }
|
||||
end
|
||||
|
||||
def update
|
||||
update! { edit_admin_content_url(@content_type.slug, @content.id) }
|
||||
update! { after_create_or_update_url }
|
||||
end
|
||||
|
||||
def sort
|
||||
@ -43,9 +53,31 @@ module Admin
|
||||
set_content_type
|
||||
end
|
||||
|
||||
def after_create_or_update_url
|
||||
if params[:breadcrumb_alias].blank?
|
||||
edit_admin_content_url(@content_type.slug, @content.id)
|
||||
else
|
||||
self.breadcrumb_url
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_content
|
||||
authorize! params[:action].to_sym, ContentInstance
|
||||
end
|
||||
|
||||
def breadcrumb_root
|
||||
return nil if params[:breadcrumb_alias].blank?
|
||||
|
||||
@breadcrumb_root ||= resource.send(params[:breadcrumb_alias].to_sym)
|
||||
end
|
||||
|
||||
def breadcrumb_url
|
||||
edit_admin_content_url(self.breadcrumb_root._parent.slug, self.breadcrumb_root)
|
||||
end
|
||||
|
||||
def back_url
|
||||
self.breadcrumb_root ? self.breadcrumb_url : admin_contents_url(@content_type.slug)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -45,6 +45,30 @@ module Admin::CustomFieldsHelper
|
||||
end.compact
|
||||
end
|
||||
|
||||
def options_for_reverse_lookups(my_content_type)
|
||||
klass_name = my_content_type.content_klass.to_s
|
||||
|
||||
[].tap do |options|
|
||||
ContentType.where(:'content_custom_fields.kind' => 'has_one', :'content_custom_fields.target' => klass_name).each do |content_type|
|
||||
content_type.content_custom_fields.find_all { |f| f.has_one? && f.target == klass_name }.each do |field|
|
||||
options << {
|
||||
:klass => content_type.content_klass.to_s,
|
||||
:label => field.label,
|
||||
:name => field._name
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def filter_options_for_reverse_has_many(contents, reverse_lookup, object)
|
||||
# Only display items which don't belong to a different object
|
||||
contents.reject do |c|
|
||||
owner = c.send(reverse_lookup.to_sym)
|
||||
!(owner.nil? || owner == object._id)
|
||||
end
|
||||
end
|
||||
|
||||
def options_for_has_one(field, value)
|
||||
self.options_for_has_one_or_has_many(field) do |groups|
|
||||
grouped_options_for_select(groups.collect do |g|
|
||||
@ -57,16 +81,20 @@ module Admin::CustomFieldsHelper
|
||||
end
|
||||
end
|
||||
|
||||
def options_for_has_many(field)
|
||||
self.options_for_has_one_or_has_many(field)
|
||||
def options_for_has_many(field, content = nil)
|
||||
self.options_for_has_one_or_has_many(field, content)
|
||||
end
|
||||
|
||||
def options_for_has_one_or_has_many(field, &block)
|
||||
def options_for_has_one_or_has_many(field, content = nil, &block)
|
||||
content_type = field.target.constantize._parent
|
||||
|
||||
if content_type.groupable?
|
||||
grouped_contents = content_type.list_or_group_contents
|
||||
|
||||
grouped_contents.each do |g|
|
||||
g[:items] = filter_options_for_reverse_has_many(g[:items], field.reverse_lookup, content)
|
||||
end if field.reverse_has_many?
|
||||
|
||||
if block_given?
|
||||
block.call(grouped_contents)
|
||||
else
|
||||
@ -80,8 +108,36 @@ module Admin::CustomFieldsHelper
|
||||
end
|
||||
else
|
||||
contents = content_type.ordered_contents
|
||||
|
||||
if field.reverse_has_many?
|
||||
contents = filter_options_for_reverse_has_many(contents, field.reverse_lookup, content)
|
||||
end
|
||||
|
||||
contents.collect { |c| [c._label, c._id] }
|
||||
end
|
||||
end
|
||||
|
||||
def has_many_data_to_js(field, content)
|
||||
options = {
|
||||
:taken_ids => content.send(field._alias.to_sym).ids
|
||||
}
|
||||
|
||||
if !content.new_record? && field.reverse_has_many?
|
||||
url_options = {
|
||||
:breadcrumb_alias => field.reverse_lookup_alias,
|
||||
"content[#{field.reverse_lookup_alias}]" => content._id
|
||||
}
|
||||
|
||||
options.merge!(
|
||||
:new_item => {
|
||||
:label => t('admin.contents.form.has_many.new_item'),
|
||||
:url => new_admin_content_url(field.target_klass._parent.slug, url_options)
|
||||
},
|
||||
:edit_item_url => edit_admin_content_url(field.target_klass._parent.slug, 42, url_options)
|
||||
)
|
||||
end
|
||||
|
||||
collection_to_js(options_for_has_many(field, content), options)
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -5,7 +5,6 @@ class Site
|
||||
## Extensions ##
|
||||
extend Extensions::Site::SubdomainDomains
|
||||
extend Extensions::Site::FirstInstallation
|
||||
extend Extensions::Site::FirstInstallation
|
||||
include Extensions::Shared::Seo
|
||||
|
||||
## fields ##
|
||||
|
@ -18,4 +18,4 @@
|
||||
|
||||
= render 'admin/shared/form_actions', :back_url => admin_contents_url(@content_type.slug_was), :button_label => :update
|
||||
|
||||
= render 'admin/custom_fields/edit_field'
|
||||
= render 'admin/custom_fields/edit_field', :content_type => @content_type
|
@ -14,4 +14,4 @@
|
||||
|
||||
= render 'admin/shared/form_actions', :back_url => admin_pages_url, :button_label => :create
|
||||
|
||||
= render 'admin/custom_fields/edit_field'
|
||||
= render 'admin/custom_fields/edit_field', :content_type => @content_type
|
@ -1,4 +1,7 @@
|
||||
- title t('.title', :type => @content_type.name.capitalize)
|
||||
- if breadcrumb_root
|
||||
- title t('.title.breadcrumb', :root => link_to(breadcrumb_root._label, breadcrumb_url), :type => @content_type.name.capitalize)
|
||||
- else
|
||||
- title t('.title.default', :type => @content_type.name.capitalize)
|
||||
|
||||
- content_for :submenu do
|
||||
= render 'admin/shared/menu/contents'
|
||||
@ -18,4 +21,4 @@
|
||||
|
||||
= render 'form', :f => form
|
||||
|
||||
= render 'admin/shared/form_actions', :back_url => admin_contents_url(@content_type.slug), :button_label => :update
|
||||
= render 'admin/shared/form_actions', :back_url => back_url, :button_label => :update
|
@ -1,4 +1,7 @@
|
||||
- title t('.title', :type => @content_type.name.capitalize)
|
||||
- if breadcrumb_root
|
||||
- title t('.title.breadcrumb', :root => link_to(breadcrumb_root._label, breadcrumb_url), :type => @content_type.name.capitalize)
|
||||
- else
|
||||
- title t('.title.default', :type => @content_type.name.capitalize)
|
||||
|
||||
- content_for :submenu do
|
||||
= render 'admin/shared/menu/contents'
|
||||
@ -16,4 +19,4 @@
|
||||
|
||||
= render 'form', :f => form
|
||||
|
||||
= render 'admin/shared/form_actions', :back_url => admin_contents_url(@content_type.slug), :button_label => :create
|
||||
= render 'admin/shared/form_actions', :back_url => back_url, :button_label => :create
|
@ -9,8 +9,12 @@
|
||||
= g.input :hint
|
||||
= g.input :text_formatting, :as => 'select', :collection => options_for_text_formatting, :include_blank => false, :wrapper_html => { :style => 'display: none' }
|
||||
= g.input :target, :as => 'select', :collection => options_for_association_target, :include_blank => false, :wrapper_html => { :style => 'display: none' }
|
||||
= g.input :reverse_lookup, :as => 'select', :collection => [], :include_blank => true, :wrapper_html => { :style => 'display: none' }
|
||||
|
||||
.popup-actions
|
||||
%p
|
||||
%button.button.light{ :type => 'submit' }
|
||||
%span= t('admin.shared.form_actions.update')
|
||||
|
||||
%script{ :type => 'text/javascript', :name => 'reverse_lookups' }
|
||||
!= collection_to_js(options_for_reverse_lookups(content_type))
|
@ -25,6 +25,8 @@
|
||||
|
||||
%input{ :name => '{{base_name}}[target]', :value => '{{{target}}}', :type => 'hidden', :'data-field' => 'target' }
|
||||
|
||||
%input{ :name => '{{base_name}}[reverse_lookup]', :value => '{{{reverse_lookup}}}', :type => 'hidden', :'data-field' => 'reverse_lookup' }
|
||||
|
||||
%input{ :name => '{{base_name}}[label]', :value => '{{{label}}}', :type => 'text', :'data-field' => 'label' }
|
||||
|
||||
—
|
||||
|
@ -25,9 +25,11 @@
|
||||
{{/if_template}}
|
||||
|
||||
%span.actions
|
||||
- if field.reverse_lookup?
|
||||
= link_to image_tag('admin/form/pen.png'), '#', :class => 'edit first'
|
||||
= link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove'
|
||||
%button{ :class => 'button light mini add', :type => 'button' }
|
||||
%span!= t('admin.buttons.new_item')
|
||||
|
||||
%script{ :type => 'text/javascript', :name => 'data' }
|
||||
!= collection_to_js(options_for_has_many(field), :taken_ids => form.object.send(field._alias.to_sym).ids)
|
||||
!= has_many_data_to_js(field, form.object)
|
@ -1,5 +1,13 @@
|
||||
- field.target.constantize.reload_parent! # to make sure all the contents from the parent are loaded
|
||||
|
||||
- if breadcrumb_root && params[:breadcrumb_alias] == field._alias
|
||||
|
||||
= form.hidden_field field._name.to_sym
|
||||
|
||||
= hidden_field_tag 'breadcrumb_alias', field._alias
|
||||
|
||||
- else
|
||||
|
||||
- selected_id = form.object.send(field._alias.to_sym).try(:_id)
|
||||
|
||||
= form.input field._alias.to_sym, :label => field.label, :hint => field.hint, :input_html => { :class => 'has_one' }, :as => :select, :collection => options_for_has_one(field, selected_id), :selected => selected_id, :required => required
|
||||
|
@ -237,7 +237,6 @@ de:
|
||||
asc: Aufsteigend
|
||||
desc: Absteigend
|
||||
|
||||
|
||||
contents:
|
||||
index:
|
||||
title: '"%{type}" anzeigen'
|
||||
@ -251,9 +250,16 @@ de:
|
||||
list:
|
||||
no_items: "Momentan gibt es keine Elemente. Klicke einfach <a href='%{url}'>hier</a>, um das erste Element zu erstellen."
|
||||
new:
|
||||
title: '%{type} — neues Element'
|
||||
title:
|
||||
default: '%{type} — neues Element'
|
||||
breadcrumb: '%{root} » %{type} — Element bearbeiten'
|
||||
edit:
|
||||
title: '%{type} — Element bearbeiten'
|
||||
title:
|
||||
default: '%{type} — editing item'
|
||||
breadcrumb: '%{root} » %{type} — Element bearbeiten'
|
||||
form:
|
||||
has_many:
|
||||
new_item: Neues Element
|
||||
|
||||
image_picker:
|
||||
link: Füge ein Bild in den Code ein
|
||||
|
@ -71,6 +71,8 @@ en:
|
||||
edit_categories: Edit options
|
||||
file:
|
||||
delete_file: Delete file
|
||||
has_many:
|
||||
empty: Empty
|
||||
index:
|
||||
is_required: is required
|
||||
default_label: Field name
|
||||
@ -246,9 +248,16 @@ en:
|
||||
list:
|
||||
no_items: "There are no items for now. Just click <a href=\"%{url}\">here</a> to create the first one."
|
||||
new:
|
||||
title: '%{type} — new item'
|
||||
title:
|
||||
default: '%{type} — new item'
|
||||
breadcrumb: '%{root} » %{type} — new item'
|
||||
edit:
|
||||
title: '%{type} — editing item'
|
||||
title:
|
||||
default: '%{type} — editing item'
|
||||
breadcrumb: '%{root} » %{type} — editing item'
|
||||
form:
|
||||
has_many:
|
||||
new_item: New item
|
||||
|
||||
image_picker:
|
||||
link: Insert an image into the code
|
||||
|
@ -232,9 +232,16 @@ es:
|
||||
list:
|
||||
no_items: "No hay ningún elemento. Haga click <a href=\"%{url}\">aquí</a> para crear el primero."
|
||||
new:
|
||||
title: '%{type} — nuevo elemento'
|
||||
title:
|
||||
default: '%{type} — nuevo elemento'
|
||||
breadcrumb: '%{root} » %{type} — nuevo elemento'
|
||||
edit:
|
||||
title: '%{type} — editando elemento'
|
||||
title:
|
||||
default: '%{type} — editando elemento'
|
||||
breadcrumb: '%{root} » %{type} — editando elemento'
|
||||
form:
|
||||
has_many:
|
||||
new_item: Nuevo elemento
|
||||
|
||||
image_picker:
|
||||
link: Insertar una imagen el el código
|
||||
|
@ -71,6 +71,8 @@ fr:
|
||||
edit_categories: Editer options
|
||||
file:
|
||||
delete_file: Supprimer fichier
|
||||
has_many:
|
||||
empty: Vide
|
||||
index:
|
||||
is_required: est obligatoire
|
||||
default_label: Nom du champ
|
||||
@ -248,9 +250,16 @@ fr:
|
||||
list:
|
||||
no_items: "Il n'existe pas d'éléments. Vous pouvez commencer par créer un <a href='%{url}'>ici</a>"
|
||||
new:
|
||||
title: '%{type} — nouvel élément'
|
||||
title:
|
||||
default: '%{type} — nouvel élément'
|
||||
breadcrumb: '%{root} » %{type} — nouvel élément'
|
||||
edit:
|
||||
title: '%{type} — édition élément'
|
||||
title:
|
||||
default: '%{type} — édition élément'
|
||||
breadcrumb: '%{root} » %{type} — édition élément'
|
||||
form:
|
||||
has_many:
|
||||
new_item: Nouvel élément
|
||||
|
||||
image_picker:
|
||||
link: Insérer une image dans le code
|
||||
|
@ -245,9 +245,16 @@ it:
|
||||
list:
|
||||
no_items: "Per ora non ci sono elementi. Clicca <a href=\"%{url}\">qui</a> per creare il primo."
|
||||
new:
|
||||
title: '%{type} — nuovo elemento'
|
||||
title:
|
||||
default: '%{type} — nuovo elemento'
|
||||
breadcrumb: '%{root} » %{type} — nuovo elemento'
|
||||
edit:
|
||||
title: '%{type} — modifica elemento'
|
||||
title:
|
||||
default: '%{type} — modifica elemento'
|
||||
breadcrumb: '%{root} » %{type} — modifica elemento'
|
||||
form:
|
||||
has_many:
|
||||
new_item: Nuovo elemento
|
||||
|
||||
image_picker:
|
||||
link: Inserisci un immagine nel codice
|
||||
|
@ -230,9 +230,16 @@ nl:
|
||||
list:
|
||||
no_items: "Er zijn momenteel geen items. Klik hier <a href=\"%{url}\">here</a> om de eerste te maken."
|
||||
new:
|
||||
title: '%{type} — nieuw item'
|
||||
title:
|
||||
default: '%{type} — nieuw item'
|
||||
breadcrumb: '%{root} » %{type} — nieuw item'
|
||||
edit:
|
||||
title: '%{type} — wijzig item'
|
||||
title:
|
||||
default: '%{type} — wijzig item'
|
||||
breadcrumb: '%{root} » %{type} — wijzig item'
|
||||
form:
|
||||
has_many:
|
||||
new_item: Nieuw item
|
||||
|
||||
image_picker:
|
||||
link: Voeg een afbeelding toe aan de code
|
||||
|
@ -226,9 +226,16 @@ pt-BR:
|
||||
list:
|
||||
no_items: "Não existem itens ainda. Clique <a href=\"%{url}\">aqui</a> para criar o primeiro."
|
||||
new:
|
||||
title: '%{type} — novo item'
|
||||
title:
|
||||
default: '%{type} — novo item'
|
||||
breadcrumb: '%{root} » %{type} — novo item'
|
||||
edit:
|
||||
title: '%{type} — editando item'
|
||||
title:
|
||||
default: '%{type} — editando item'
|
||||
breadcrumb: '%{root} » %{type} — editando item'
|
||||
form:
|
||||
has_many:
|
||||
new_item: Novo item
|
||||
|
||||
image_picker:
|
||||
link: Insira uma imagem no código
|
||||
|
2
doc/TODO
2
doc/TODO
@ -21,7 +21,7 @@ BACKLOG:
|
||||
- sync data
|
||||
- import only theme assets
|
||||
- endless pagination
|
||||
- overide sort for contents
|
||||
- override sort for contents
|
||||
- tooltip to explain the difference between 1.) Admin 2.) Author 3.) Designer?
|
||||
- [bushido] guiders / welcome page / devise cas authentication (SSO)
|
||||
|
||||
|
61
features/admin/content_types/has_many_reverse.feature
Normal file
61
features/admin/content_types/has_many_reverse.feature
Normal file
@ -0,0 +1,61 @@
|
||||
Feature: Set up a has many reverse relationship
|
||||
In order to have a true 1:N relationship between 2 content types
|
||||
As an administrator
|
||||
I want to set up a reverse a has many relationship
|
||||
|
||||
Background:
|
||||
Given I have the site: "test site" set up
|
||||
And I have a custom model named "Clients" with
|
||||
| label | kind | required |
|
||||
| Name | string | true |
|
||||
| Description | string | false |
|
||||
And I have a custom model named "Projects" with
|
||||
| label | kind | required | target |
|
||||
| Name | string | true | |
|
||||
| Description | text | false | |
|
||||
| Client | has_one | false | Clients |
|
||||
And I set up a reverse has_many relationship between "Clients" and "Projects"
|
||||
And I have entries for "Clients" with
|
||||
| name | description |
|
||||
| Apple Inc | Lorem ipsum |
|
||||
| NoCoffee | Lorem ipsum... |
|
||||
And I have entries for "Projects" with
|
||||
| name | description |
|
||||
| My sexy project | Lorem ipsum |
|
||||
| Foo project | Lorem ipsum... |
|
||||
| Bar project | Lorem ipsum... |
|
||||
|
||||
And I am an authenticated user
|
||||
|
||||
@javascript
|
||||
Scenario: I do not see the "Add Item" button for new parent
|
||||
When I go to the "Clients" model creation page
|
||||
Then "New item" should not be an option for "label"
|
||||
|
||||
@javascript
|
||||
Scenario: I attach already created items for an existing parent and save it
|
||||
When I go to the "Clients" model list page
|
||||
And I follow "Apple Inc"
|
||||
And I wait until ".has-many-selector ul li.template" is visible
|
||||
Then "My sexy project" should be an option for "label"
|
||||
When I select "My sexy project" from "label"
|
||||
And I press "+ add"
|
||||
And "My sexy project" should not be an option for "label"
|
||||
When I press "Save"
|
||||
And I wait until ".has-many-selector ul li.template" is visible
|
||||
Then "My sexy project" should not be an option for "label"
|
||||
|
||||
@javascript
|
||||
Scenario: I create a new item and attach it
|
||||
When I go to the "Clients" model list page
|
||||
And I follow "Apple Inc"
|
||||
And I wait until ".has-many-selector ul li.template" is visible
|
||||
And I press "+ add"
|
||||
Then I should see "Apple Inc » Projects — new item"
|
||||
And I should not see "Client" within the main form
|
||||
When I fill in "Name" with "iPad"
|
||||
And I press "Create"
|
||||
Then I should see "Content was successfully created."
|
||||
When I wait until ".has-many-selector ul li.template" is visible
|
||||
Then I should see "iPad"
|
||||
And "iPad" should not be an option for "label"
|
@ -2,12 +2,33 @@ Given %r{^I have a custom model named "([^"]*)" with$} do |name, fields|
|
||||
site = Site.first
|
||||
content_type = Factory.build(:content_type, :site => site, :name => name)
|
||||
fields.hashes.each do |field|
|
||||
f = content_type.content_custom_fields.build field
|
||||
if (target_name = field.delete('target')).present?
|
||||
target_content_type = site.content_types.where(:name => target_name).first
|
||||
field['target'] = target_content_type.content_klass.to_s
|
||||
end
|
||||
|
||||
content_type.content_custom_fields.build field
|
||||
end
|
||||
content_type.valid?
|
||||
content_type.save.should be_true
|
||||
end
|
||||
|
||||
Given /^I set up a reverse has_many relationship between "([^"]*)" and "([^"]*)"$/ do |name_1, name_2|
|
||||
site = Site.first
|
||||
|
||||
content_type_1 = site.content_types.where(:name => name_1).first
|
||||
content_type_2 = site.content_types.where(:name => name_2).first
|
||||
|
||||
content_type_1.content_custom_fields.build({
|
||||
:label => name_2,
|
||||
:kind => 'has_many',
|
||||
:target => content_type_2.content_klass.to_s,
|
||||
:reverse_lookup => content_type_2.content_klass.custom_field_alias_to_name(name_1.downcase.singularize)
|
||||
})
|
||||
|
||||
content_type_1.save.should be_true
|
||||
end
|
||||
|
||||
Given %r{^I have "([^"]*)" as "([^"]*)" values of the "([^"]*)" model$} do |values, field, name|
|
||||
content_type = ContentType.where(:name => name).first
|
||||
field = content_type.content_custom_fields.detect { |f| f.label == field }
|
||||
@ -45,3 +66,4 @@ end
|
||||
Then %r{^I should see once the "([^"]*)" field$} do |field|
|
||||
page.all(:css, "#content_#{field.underscore.downcase}_input").size.should == 1
|
||||
end
|
||||
|
||||
|
@ -5,3 +5,14 @@ end
|
||||
Then /^I should get a download with the filename "([^\"]*)"$/ do |filename|
|
||||
page.response_headers['Content-Disposition'].should include("filename=\"#{filename}\"")
|
||||
end
|
||||
|
||||
When /^I wait until "([^"]*)" is visible$/ do |selector|
|
||||
page.has_css?("#{selector}", :visible => true)
|
||||
end
|
||||
|
||||
Then /^"([^"]*)" should( not)? be an option for "([^"]*)"(?: within "([^\"]*)")?$/ do |value, negate, field, selector|
|
||||
with_scope(selector) do
|
||||
expectation = negate ? :should_not : :should
|
||||
field_labeled(field).first(:xpath, ".//option[text() = '#{value}']").send(expectation, be_present)
|
||||
end
|
||||
end
|
15
features/step_definitions/within_steps.rb
Normal file
15
features/step_definitions/within_steps.rb
Normal file
@ -0,0 +1,15 @@
|
||||
# http://mislav.uniqpath.com/2010/09/cuking-it-right/
|
||||
|
||||
{
|
||||
|
||||
# 'as a movie title in the results' => 'ol.movies h1',
|
||||
# 'in a button' => 'button, input[type=submit]',
|
||||
# 'in the navigation' => 'nav'
|
||||
}.
|
||||
each do |within, selector|
|
||||
Then /^(.+) #{within}$/ do |step|
|
||||
with_scope(selector) do
|
||||
Then step
|
||||
end
|
||||
end
|
||||
end
|
@ -34,6 +34,9 @@ module NavigationHelpers
|
||||
when /the "(.*)" model list page/
|
||||
content_type = Site.first.content_types.where(:name => $1).first
|
||||
admin_contents_path(content_type.slug)
|
||||
when /the "(.*)" model creation page/
|
||||
content_type = Site.first.content_types.where(:name => $1).first
|
||||
new_admin_content_path(content_type.slug)
|
||||
when /the "(.*)" model edition page/
|
||||
content_type = Site.first.content_types.where(:name => $1).first
|
||||
edit_admin_content_type_path(content_type)
|
||||
|
@ -11,6 +11,9 @@ module HtmlSelectorsHelpers
|
||||
when "the page"
|
||||
"html > body"
|
||||
|
||||
when "the main form"
|
||||
"form.formtastic"
|
||||
|
||||
# Add more mappings here.
|
||||
# Here is an example that pulls values out of the Regexp:
|
||||
#
|
||||
|
@ -42,7 +42,7 @@ Gem::Specification.new do |s|
|
||||
s.add_dependency 'dragonfly', '~> 0.9.1'
|
||||
s.add_dependency 'rack-cache'
|
||||
|
||||
s.add_dependency 'custom_fields', '1.0.0.beta.21'
|
||||
s.add_dependency 'custom_fields', '1.0.0.beta.22'
|
||||
s.add_dependency 'cancan', '~> 1.6.0'
|
||||
s.add_dependency 'fog', '0.8.2'
|
||||
s.add_dependency 'mimetype-fu'
|
||||
|
@ -5,6 +5,7 @@ $(document).ready(function() {
|
||||
var template = wrapper.find('script[name=template]').html();
|
||||
var baseInputName = wrapper.find('script[name=template]').attr('data-base-input-name');
|
||||
var data = eval(wrapper.find('script[name=data]').html());
|
||||
var reverseLookupData = eval($('script[name=reverse_lookups]').html());
|
||||
var index = 0;
|
||||
|
||||
var domFieldVal = function(domField, fieldName, val) {
|
||||
@ -20,6 +21,39 @@ $(document).ready(function() {
|
||||
return (typeof(val) == 'undefined' ? domBoxAttr(fieldName).val() : domBoxAttr(fieldName).val(val));
|
||||
}
|
||||
|
||||
var setupReverseLookupDropdown = function(currentVal) {
|
||||
var dropdown = $('#fancybox-inner #edit-custom-field #custom_fields_field_reverse_lookup');
|
||||
|
||||
// Get the target content_type
|
||||
var targetClassEl = $('#fancybox-inner #edit-custom-field #custom_fields_field_target');
|
||||
|
||||
var callback = function() {
|
||||
// Clear the reverse_lookup dropdown
|
||||
dropdown.find('option').remove();
|
||||
|
||||
// Add the initial blank entry
|
||||
dropdown.append($('<option>'));
|
||||
|
||||
// Go through the collection and add the appropriate elements
|
||||
collection = reverseLookupData['collection'];
|
||||
for (var i = 0; i < collection.length; i++) {
|
||||
element = collection[i];
|
||||
if (element.klass === targetClassEl.val()) {
|
||||
var option = $('<option>', { value: element.name });
|
||||
option.text(element.label);
|
||||
dropdown.append(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback(); // first call
|
||||
|
||||
targetClassEl.change(callback);
|
||||
|
||||
// Set initial value
|
||||
dropdown.val(currentVal);
|
||||
}
|
||||
|
||||
/* ___ Register all the different events when a field is added (destroy, edit details, ...etc) ___ */
|
||||
|
||||
var registerFieldEvents = function(field, domField) {
|
||||
@ -50,7 +84,7 @@ $(document).ready(function() {
|
||||
// edit
|
||||
domField.find('a.edit').click(function(e) {
|
||||
var link = $(this);
|
||||
var attributes = ['_alias', 'hint', 'text_formatting', 'target'];
|
||||
var attributes = ['_alias', 'hint', 'text_formatting', 'target', 'reverse_lookup'];
|
||||
|
||||
$.fancybox({
|
||||
titleShow: false,
|
||||
@ -61,7 +95,7 @@ $(document).ready(function() {
|
||||
$.each(attributes, function(index, name) {
|
||||
try {
|
||||
var val = domBoxAttrVal(name).trim();
|
||||
if (val != '') domFieldVal(domField, name, val);
|
||||
if (val != '' || name == 'reverse_lookup') domFieldVal(domField, name, val);
|
||||
} catch(e) {}
|
||||
});
|
||||
domBoxAttr('text_formatting').parent().hide();
|
||||
@ -70,16 +104,28 @@ $(document).ready(function() {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
});
|
||||
|
||||
var reverseLookupInitialVal = null;
|
||||
|
||||
// copy current val to the form in the box
|
||||
$.each(attributes, function(index, name) {
|
||||
var val = domFieldVal(domField, name).trim();
|
||||
if (val == '' && name == '_alias') val = makeSlug(domFieldVal(domField, 'label'));
|
||||
|
||||
if (name == 'reverse_lookup') reverseLookupInitialVal = val;
|
||||
|
||||
domBoxAttrVal(name, val);
|
||||
});
|
||||
|
||||
if (domFieldVal(domField, 'kind').toLowerCase() == 'text') domBoxAttr('text_formatting').parents('li').show();
|
||||
if (domFieldVal(domField, 'kind').toLowerCase() == 'has_one' ||
|
||||
domFieldVal(domField, 'kind').toLowerCase() == 'has_many') domBoxAttr('target').parents('li').show();
|
||||
if (domFieldVal(domField, 'kind').toLowerCase() == 'has_many')
|
||||
domBoxAttr('reverse_lookup').parents('li').show();
|
||||
|
||||
// Configure the reverse_lookup dropdown and populate it
|
||||
setupReverseLookupDropdown(reverseLookupInitialVal);
|
||||
|
||||
setTimeout($.fancybox.resize, 1500);
|
||||
}
|
||||
});
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
|
@ -9,6 +9,14 @@ $(document).ready(function() {
|
||||
var populateSelect = function(context) {
|
||||
context.select.find('optgroup, option').remove();
|
||||
|
||||
if (context.data.new_item) {
|
||||
var newItemInfo = context.data.new_item;
|
||||
var option = new Option(newItemInfo.label, newItemInfo.url, true, true);
|
||||
context.select.append(option);
|
||||
|
||||
context.select.append(new Option('-'.repeat(newItemInfo.label.length), '', false, false));
|
||||
}
|
||||
|
||||
for (var i = 0; i < context.data.collection.length; i++) {
|
||||
var obj = context.data.collection[i];
|
||||
|
||||
@ -19,7 +27,7 @@ $(document).ready(function() {
|
||||
for (var j = 0; j < obj.items.length; j++) {
|
||||
var innerObj = obj.items[j];
|
||||
if ($.inArray(innerObj[1], context.data.taken_ids) == -1) {
|
||||
optgroup.append(new Option(innerObj[0], innerObj[1], true, true));
|
||||
optgroup.append(new Option(innerObj[0], innerObj[1], false, false));
|
||||
size++;
|
||||
}
|
||||
}
|
||||
@ -28,7 +36,7 @@ $(document).ready(function() {
|
||||
} else {
|
||||
if ($.inArray(obj[1], context.data.taken_ids) == -1)
|
||||
{
|
||||
var option = new Option("", obj[1], true, true);
|
||||
var option = new Option("", obj[1], false, false);
|
||||
$(option).text(obj[0]);
|
||||
context.select.append(option);
|
||||
}
|
||||
@ -71,6 +79,15 @@ $(document).ready(function() {
|
||||
}
|
||||
|
||||
var registerElementEvents = function(context, data, domElement) {
|
||||
// edit
|
||||
domElement.find('a.edit').click(function(e) {
|
||||
var url = context.data.edit_item_url.replace(/\/42\//, '/' + data.id + '/');
|
||||
|
||||
window.location.href = url;
|
||||
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
})
|
||||
|
||||
// remove
|
||||
domElement.find('a.remove').click(function(e) {
|
||||
domElement.remove();
|
||||
@ -91,6 +108,14 @@ $(document).ready(function() {
|
||||
label: context.select.find('option:selected').text()
|
||||
};
|
||||
|
||||
if (newElement.id == '') return;
|
||||
|
||||
if (newElement.id.match(/^http:\/\//)) {
|
||||
window.location.href = newElement.id;
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
addId(context, newElement.id);
|
||||
|
||||
addElement(context, newElement, { refreshPosition: true });
|
||||
|
@ -17,6 +17,11 @@ function makeSlug(val, sep) { // code largely inspired by http://www.thewebsitet
|
||||
String.prototype.trim = function() {
|
||||
return this.replace(/^\s+/g, '').replace(/\s+$/g, '');
|
||||
}
|
||||
|
||||
String.prototype.repeat = function(num) {
|
||||
for (var i = 0, buf = ""; i < num; i++) buf += this;
|
||||
return buf;
|
||||
}
|
||||
})();
|
||||
|
||||
Object.size = function(obj) {
|
||||
|
@ -87,9 +87,10 @@ div.asset-picker ul li .more { top: 8px; }
|
||||
/* ___ form action ___ */
|
||||
|
||||
#fancybox-inner .popup-actions {
|
||||
position: absolute;
|
||||
/* position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
*/
|
||||
height: 61px;
|
||||
width: 100%;
|
||||
background: #8b8d9a;
|
||||
|
@ -492,6 +492,7 @@ form.formtastic fieldset ol li.has-many ul li.template span.actions {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
form.formtastic fieldset ol li.has-many ul li.template span.actions a.edit,
|
||||
form.formtastic fieldset ol li.has-many ul li.template span.actions a.remove {
|
||||
display: none;
|
||||
}
|
||||
|
@ -80,9 +80,17 @@ body {
|
||||
border-bottom: 1px dotted #bbbbbd;
|
||||
}
|
||||
|
||||
#content div.inner h2 a {
|
||||
color: #1F82BC;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#content div.inner h2 a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#content div.inner h2 a.editable {
|
||||
padding: 2px 25px 2px 6px;
|
||||
text-decoration: none;
|
||||
color: #1e1f26;
|
||||
outline: none;
|
||||
}
|
||||
@ -90,6 +98,7 @@ body {
|
||||
#content div.inner h2 a.editable:hover {
|
||||
background: #fffbe5 url(/images/admin/form/pen.png) no-repeat right 5px;
|
||||
border-bottom: 1px dotted #efe4a5;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#content div.inner h3 {
|
||||
|
Loading…
Reference in New Issue
Block a user