implement the UI for the many_to_many relationship (WIP)

This commit is contained in:
did 2012-02-03 01:50:45 +01:00
parent 9e9fe49ccf
commit 2a911d912c
13 changed files with 238 additions and 23 deletions

View File

@ -12,6 +12,12 @@ class Locomotive.Models.ContentEntry extends Backbone.Model
collection = new Locomotive.Models.ContentEntriesCollection(@get(name))
@set_attribute name, collection
_.each @get('_many_to_many_fields'), (field) =>
name = field[0]
collection = new Locomotive.Models.ContentEntriesCollection(@get(name))
collection.comparator = (entry) -> entry.get('__position') || 0
@set_attribute name, collection
set_attribute: (attribute, value) ->
data = {}
data[attribute] = value
@ -40,6 +46,10 @@ class Locomotive.Models.ContentEntry extends Backbone.Model
name = field[0]
hash["#{name}_attributes"] = @get(name).toMinJSON()
_.each @get('_many_to_many_fields'), (field) => # include the many_to_many relationships
name = field[0]; setter_name = field[1]
hash[setter_name] = @get(name).sort().map (entry) => entry.id
class Locomotive.Models.ContentEntriesCollection extends Backbone.Collection
model: Locomotive.Models.ContentEntry

View File

@ -17,7 +17,7 @@ class Locomotive.Models.CustomField extends Backbone.Model
['class_name', 'inverse_of', 'ui_enabled']
is_relationship_type: ->
_.include(['belongs_to', 'has_many'], @get('type'))
_.include(['belongs_to', 'has_many', 'many_to_many'], @get('type'))
toJSONForSave: ->
_.tap {}, (hash) =>

View File

@ -10,6 +10,8 @@ class Locomotive.Views.ContentEntries.FormView extends Locomotive.Views.Shared.F
_has_many_field_views: []
_many_to_many_field_views: []
events:
'submit': 'save'
@ -33,6 +35,8 @@ class Locomotive.Views.ContentEntries.FormView extends Locomotive.Views.Shared.F
@enable_has_many_fields()
@enable_many_to_many_fields()
@slugify_label_field()
return @
@ -67,7 +71,16 @@ class Locomotive.Views.ContentEntries.FormView extends Locomotive.Views.Shared.F
@_has_many_field_views.push(view)
@$("##{@model.paramRoot}_#{name}_input").append(view.render().el)
@$("##{@model.paramRoot}_#{name}_input label").after(view.render().el)
enable_many_to_many_fields: ->
_.each @model.get('_many_to_many_fields'), (field) =>
name = field[0]
view = new Locomotive.Views.Shared.Fields.ManyToManyView model: @model, name: name, all_entries: @options["all_#{name}_entries"]
@_many_to_many_field_views.push(view)
@$("##{@model.paramRoot}_#{name}_input label").after(view.render().el)
slugify_label_field: ->
@$('li.input.highlighted > input[type=text]').slugify(target: @$('#content_entry__slug'))
@ -88,6 +101,7 @@ class Locomotive.Views.ContentEntries.FormView extends Locomotive.Views.Shared.F
remove: ->
_.each @_file_field_views, (view) => view.remove()
_.each @_has_many_field_views, (view) => view.remove()
_.each @_many_to_many_field_views, (view) => view.remove()
super
tinyMCE_settings: ->

View File

@ -13,8 +13,6 @@ class Locomotive.Views.ContentTypes.CustomFieldEntryView extends Backbone.View
initialize: ->
@inverse_of_list = @options.parent_view.options.inverse_of_list
console.log(@model.get('inverse_of'))
@model.bind 'change', (custom_field) =>
@switch_to_type() if @model.hasChanged('type')
@fetch_inverse_of_list() if @model.hasChanged('class_name') && @model.get('class_name')?
@ -51,7 +49,7 @@ class Locomotive.Views.ContentTypes.CustomFieldEntryView extends Backbone.View
when 'belongs_to'
@$('li.input.localized').hide()
@$('li.input.class-name').show()
when 'has_many'
when 'has_many', 'many_to_many'
@$('li.input.localized').hide()
@$('li.input.class-name').show()
@$('li.input.inverse-of').show()
@ -60,7 +58,7 @@ class Locomotive.Views.ContentTypes.CustomFieldEntryView extends Backbone.View
fetch_inverse_of_list: ->
@$('li.input.inverse-of select option').remove()
_.each @inverse_of_list, (data) =>
_.each @inverse_of_list[@model.get('type')], (data) =>
if data.class_name == @model.get('class_name')
option = new Option(data.label, data.name, data.class_name == @model.get('inverse_of') || @inverse_of_list.length == 1)
@$('li.input.inverse-of select').append(option)

View File

@ -0,0 +1,132 @@
Locomotive.Views.Shared ||= {}
Locomotive.Views.Shared.Fields ||= {}
class Locomotive.Views.Shared.Fields.ManyToManyView extends Backbone.View
tagName: 'div'
className: 'list'
events:
'click .new-entry a.add': 'add_entry'
'keypress .new-entry select': 'add_entry'
'click ul span.actions a.remove': 'remove_entry'
template: ->
ich["#{@options.name}_list"]
entry_template: ->
ich["#{@options.name}_entry"]
initialize: ->
_.bindAll(@, 'refresh_position_entries')
@collection = @model.get(@options.name)
@all_entries = @options.all_entries
window.collection = @collection
window.bar = @all_entries
window.model = @model
render: ->
$(@el).html(@template()())
@insert_entries()
@make_entries_sortable()
@refresh_select_field()
return @
insert_entries: ->
if @collection.length > 0
@collection.each (entry) => @insert_entry(entry)
else
@$('.empty').show()
insert_entry: (entry) ->
unless @collection.get(entry.get('_id'))?
@collection.add(entry)
@$('.empty').hide()
entry_html = $(@entry_template()(label: entry.get('_label')))
entry_html.data('data-entry-id', entry.id)
@$('> ul').append(entry_html)
make_entries_sortable: ->
@sortable_list = @$('> ul').sortable
handle: '.handle'
items: 'li'
axis: 'y'
update: @refresh_position_entries
refresh_position_entries: ->
@$('> ul > li').each (index, entry_html) =>
id = $(entry_html).data('data-entry-id')
entry = @collection.get(id)
entry.set_attribute "__position", index
add_entry: (event) ->
event.stopPropagation() & event.preventDefault()
entry_id = @$('.new-entry select').val()
entry = @get_entry_from_id(entry_id)
return unless entry?
@insert_entry(entry)
@refresh_select_field()
remove_entry: (event) ->
event.stopPropagation() & event.preventDefault()
if confirm($(event.target).attr('data-confirm'))
entry = @get_entry_from_element($(event.target))
@collection.remove(entry)
$(event.target).closest('li').remove()
@$('.empty').show() if @$('> ul > li').size() == 0
@refresh_position_entries() & @refresh_select_field()
refresh_select_field: ->
@$('.new-entry select optgroup, .new-entry select option').remove()
_.each @all_entries, (entry_or_group) =>
if _.isArray(entry_or_group.entries)
group_html = $('<optgroup/>').attr('label', entry_or_group.name)
_.each entry_or_group.entries, (entry) =>
unless @collection.get(entry._id)?
option = new Option(entry._label, entry._id, false)
group_html.append(option)
@$('.new-entry select').append(group_html)
else
unless @collection.get(entry_or_group._id)?
option = new Option(entry_or_group._label, entry_or_group._id, false)
@$('.new-entry select').append(option)
get_entry_from_element: (element) ->
entry_html = $(element).closest('li')
id = $(entry_html).data('data-entry-id')
@collection.get(id)
get_entry_from_id: (id) ->
entry = null
_.each @all_entries, (entry_or_group) =>
if _.isArray(entry_or_group.entries)
entry ||= _.detect(entry_or_group.entries, (_entry) => _entry._id == id)
else
entry = entry_or_group if entry_or_group._id == id
if entry?
new Locomotive.Models.ContentEntry(entry)
else
null

View File

@ -570,6 +570,7 @@ form.formtastic {
&.label {
margin-left: 8px;
font-weight: bold;
color: #000;
}
} // ul .col

View File

@ -1,7 +1,7 @@
module Locomotive::CustomFieldsHelper
def options_for_custom_field_type
%w(string text select boolean date file belongs_to has_many).map do |type|
%w(string text select boolean date file belongs_to has_many many_to_many).map do |type|
[t("custom_fields.types.#{type}"), type]
end
end
@ -48,11 +48,12 @@ module Locomotive::CustomFieldsHelper
end
def options_for_content_type_inverse_of
[].tap do |list|
current_site.content_types.where(:'entries_custom_fields.type' => 'belongs_to').each do |content_type|
{}.tap do |hash|
current_site.content_types.where(:'entries_custom_fields.type'.in => %w(belongs_to many_to_many)).each do |content_type|
content_type.entries_custom_fields.each do |field|
if field.type == 'belongs_to'
list << {
if %w(belongs_to many_to_many).include?(field.type)
hash[field.type] ||= []
hash[field.type] << {
:label => field.label,
:name => field.name,
:class_name => content_type.klass_with_custom_fields(:entries).to_s

View File

@ -150,11 +150,11 @@ module Locomotive
content_type = Locomotive::ContentType.find($1)
if content_type.site_id != self.site_id
field.errors.add :security
field.errors.add :class_name, :security
end
else
# for now, does not allow external classes
field.errors.add :security
field.errors.add :class_name, :security
end
end

View File

@ -34,7 +34,7 @@ module Locomotive
when :date then "formatted_#{rule['name']}"
when :file then [rule['name'], "remove_#{rule['name']}"]
when :select, :belongs_to then ["#{rule['name']}_id", "position_in_#{rule['name']}"]
when :has_many then nil
when :has_many, :many_to_many then nil
else
rule['name']
end
@ -50,15 +50,19 @@ module Locomotive
end
def _file_fields
self.source.custom_fields_recipe['rules'].find_all { |rule| rule['type'] == 'file' }.map { |rule| rule['name'] }
group_fields 'file'
end
def _has_many_fields
self.source.custom_fields_recipe['rules'].find_all { |rule| rule['type'] == 'has_many' }.map { |rule| [rule['name'], rule['inverse_of']] }
group_fields('has_many') { |rule| [rule['name'], rule['inverse_of']] }
end
def _many_to_many_fields
group_fields('many_to_many') { |rule| [rule['name'], "#{rule['name'].singularize}_ids"] }
end
def included_methods
default_list = %w(_label _slug _position content_type_slug _file_fields _has_many_fields safe_attributes)
default_list = %w(_label _slug _position content_type_slug _file_fields _has_many_fields _many_to_many_fields safe_attributes)
default_list << 'errors' if !!self.options[:include_errors]
super + self.custom_fields_methods + default_list
end
@ -91,5 +95,13 @@ module Locomotive
end
end
def group_fields(type, &block)
unless block_given?
block = lambda { |rule| rule['name'] }
end
self.source.custom_fields_recipe['rules'].find_all { |rule| rule['type'] == type }.map(&block)
end
end
end

View File

@ -0,0 +1,41 @@
- if field.ui_enabled?
- target_content_type = Locomotive::ContentType.class_name_to_content_type(field.class_name, current_site)
= f.input name.to_sym,
:label => field.label,
:hint => field.hint,
:as => :'Locomotive::Empty',
:wrapper_html => { :id => "content_entry_#{name}_input", :class => 'empty relationship input' }
- content_for :head do
%script{ :type => 'text/html', :id => "#{name}_list" }
%p.empty{ :style => 'display: none' }!= t('.empty')
%ul
%hr
.new-entry
= select_tag 'entry', []
%span.actions
= link_to t('locomotive.buttons.new_item'), '#', :class => 'add'
%script{ :type => 'text/html', :id => "#{name}_entry" }
%li
.handle.col
= image_tag 'locomotive/form/icons/drag.png'
.label.col
{{label}}
%span.actions
= link_to 'x', '#', :class => 'remove', :confirm => t('locomotive.messages.confirm')
- content_for :backbone_view_data do
:plain
, all_#{name}_entries: #{target_content_type.list_or_group_entries.to_json}

View File

@ -82,6 +82,8 @@ en:
has_many:
empty: The list is empty
new_entry: + Add a new entry
many_to_many:
empty: The list is empty. Add an entry from the select box below.
form:
required: Required

View File

@ -121,6 +121,7 @@ x script to migrate existing site
x unable to remove a field
x "back to admin" link does not work if inline editor disabled
x unable to delete memberships
- editable_elements does not display the first time they get created (and if there are no existing ones)
- display by categories does not work when localized
- disallow to click twice on the submit form button (spinner ?)
- message to notify people if their browser is too old

View File

@ -74,7 +74,10 @@
for (var prefix in data) {
_buildParams(prefix, data[prefix], function(key, value) {
// console.log('append ' + key + ', ' + value);
// console.log('append ' + key + ', ' + value + ', is Array = ' + jQuery.isArray(value));
if (jQuery.isArray(value) && value.length == 0)
formData.append(key + '[]', '');
else
if (value != null)
formData.append(key, value);
});