Add the reverse has_many field feature (mainly based on pull request #142)

This commit is contained in:
did 2011-08-11 13:45:46 -07:00
parent 333934c022
commit 67d7b13f4f
37 changed files with 411 additions and 48 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,6 @@ class Site
## Extensions ##
extend Extensions::Site::SubdomainDomains
extend Extensions::Site::FirstInstallation
extend Extensions::Site::FirstInstallation
include Extensions::Shared::Seo
## fields ##

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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' }
&mdash;

View File

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

View File

@ -1,6 +1,14 @@
- field.target.constantize.reload_parent! # to make sure all the contents from the parent are loaded
- selected_id = form.object.send(field._alias.to_sym).try(:_id)
- if breadcrumb_root && params[:breadcrumb_alias] == field._alias
= 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
= 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

View File

@ -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} &mdash; neues Element'
title:
default: '%{type} &mdash; neues Element'
breadcrumb: '%{root} &raquo; %{type} &mdash; Element bearbeiten'
edit:
title: '%{type} &mdash; Element bearbeiten'
title:
default: '%{type} &mdash; editing item'
breadcrumb: '%{root} &raquo; %{type} &mdash; Element bearbeiten'
form:
has_many:
new_item: Neues Element
image_picker:
link: Füge ein Bild in den Code ein

View File

@ -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} &mdash; new item'
title:
default: '%{type} &mdash; new item'
breadcrumb: '%{root} &raquo; %{type} &mdash; new item'
edit:
title: '%{type} &mdash; editing item'
title:
default: '%{type} &mdash; editing item'
breadcrumb: '%{root} &raquo; %{type} &mdash; editing item'
form:
has_many:
new_item: New item
image_picker:
link: Insert an image into the code

View File

@ -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} &mdash; nuevo elemento'
title:
default: '%{type} &mdash; nuevo elemento'
breadcrumb: '%{root} &raquo; %{type} &mdash; nuevo elemento'
edit:
title: '%{type} &mdash; editando elemento'
title:
default: '%{type} &mdash; editando elemento'
breadcrumb: '%{root} &raquo; %{type} &mdash; editando elemento'
form:
has_many:
new_item: Nuevo elemento
image_picker:
link: Insertar una imagen el el código

View File

@ -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} &mdash; nouvel élément'
title:
default: '%{type} &mdash; nouvel élément'
breadcrumb: '%{root} &raquo; %{type} &mdash; nouvel élément'
edit:
title: '%{type} &mdash; édition élément'
title:
default: '%{type} &mdash; édition élément'
breadcrumb: '%{root} &raquo; %{type} &mdash; édition élément'
form:
has_many:
new_item: Nouvel élément
image_picker:
link: Insérer une image dans le code

View File

@ -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} &mdash; nuovo elemento'
title:
default: '%{type} &mdash; nuovo elemento'
breadcrumb: '%{root} &raquo; %{type} &mdash; nuovo elemento'
edit:
title: '%{type} &mdash; modifica elemento'
title:
default: '%{type} &mdash; modifica elemento'
breadcrumb: '%{root} &raquo; %{type} &mdash; modifica elemento'
form:
has_many:
new_item: Nuovo elemento
image_picker:
link: Inserisci un immagine nel codice

View File

@ -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} &mdash; nieuw item'
title:
default: '%{type} &mdash; nieuw item'
breadcrumb: '%{root} &raquo; %{type} &mdash; nieuw item'
edit:
title: '%{type} &mdash; wijzig item'
title:
default: '%{type} &mdash; wijzig item'
breadcrumb: '%{root} &raquo; %{type} &mdash; wijzig item'
form:
has_many:
new_item: Nieuw item
image_picker:
link: Voeg een afbeelding toe aan de code

View File

@ -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} &mdash; novo item'
title:
default: '%{type} &mdash; novo item'
breadcrumb: '%{root} &raquo; %{type} &mdash; novo item'
edit:
title: '%{type} &mdash; editando item'
title:
default: '%{type} &mdash; editando item'
breadcrumb: '%{root} &raquo; %{type} &mdash; editando item'
form:
has_many:
new_item: Novo item
image_picker:
link: Insira uma imagem no código

View File

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

View 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"

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

@ -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();

View File

@ -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 });

View File

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

View File

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

View File

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

View File

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