implement the UI for the many_to_many relationship (WIP)
This commit is contained in:
parent
9e9fe49ccf
commit
2a911d912c
@ -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
|
||||
|
@ -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) =>
|
||||
|
@ -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: ->
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
@ -570,6 +570,7 @@ form.formtastic {
|
||||
&.label {
|
||||
margin-left: 8px;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
} // ul .col
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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}
|
@ -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
|
||||
|
1
doc/TODO
1
doc/TODO
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user