У меня есть представление:
- entry = f.object
- kind = entry.new_record? ? '' : entry.kind
tr[class="entry_row f-order-entry f-order-nested-entry" data-kind=(kind.to_s)
class=(entry.created_without_order? ? 'entry_no_order' : '')
class=(entry.kind == Entry::COMPLEX ? 'f-ignore-price f-complex-order-entry' : '' )]
td.new-entry
- if entry.created_without_order?
= image_tag('new-box.png', size: '30x25', alt: 'new')
td.hidden
= f.input :id
= f.input :entry_type_id
= f.input :clinic_id
= f.input :client_id
= f.input :order_id
= f.input :kind
= f.input :number
= f.input :state
= f.input :discount_disabled, as: :string
= f.input :final_price, input_html: { class: 'f-order-entry-final-price' }
= f.input :sum, input_html: { class: 'f-order-entry-sum' }
= f.input :discount_sum, input_html: { class: 'f-order-entry-discount-sum' }
= f.input :date, as: :string, input_html: { class: 'f-order-entry-date' }
= new_fields_template(f, :members, entry.members.length)
= new_fields_template(f,
:consumables,
entry.consumables.length,
nil,
variables: { parent: :entry })
td.entry-title.tooltip-bottom
= f.input :title, input_html: { readonly: true, title: }
= entry.title
.toggle-content-button
span.toggle-content-button-number
span.toggle-content-button-icon
= edit_order_entry_performer f, @performers
= edit_order_entry_assistant f, @assistants
= edit_order_entry_referral f, @referrals
= edit_order_entry_store f, @stores
эта строка
= f.input: title, input_html: {readonly: true, title:}
формирует следующую разметку
<input class="form-control string required" autocomplete="off" readonly="readonly" title="" type="text" name="order[entries_attributes][0][title]" id="order_entries_attributes_0_title">
мы получаем динамический список сервисов (элементы добавляются щелчком по каталогу), мне нужно получить атрибут title во входных данных, так же каксамо имя (имена длинные, не всегда умещаются на экране, всплывающая подсказка отображается позже из заголовка (это настроено, мне нужно подставить только значение, равное названию самого сервиса)
вот js, который рисует линии после добавления из каталога (каталога)
FormFactory.nestedFields = function (params) {
//var form = params.form
var msgPrefix = 'page.form.' + params.model + '.nestedFields.'
var settings = params.nestedFields
var template = params.form.find('._template_' + settings.model)
var model = settings.model
PubSub.subscribe(msgPrefix + 'add', function (msg, data) {
var item = data.item
var generated = template.railsTemplate(model)
var fields = generated.fields
var html = generated.html
var response = $.extend(data, {
html: html,
fields: fields,
item: item,
msgFrom: msg
})
PubSub.publish(msgPrefix + 'render', response)
})
return {
}
}
js, которые образуют всю таблицу (здесь заинтересован только исполнитель записей - название сервиса, которое мне нужно добавитьдо
FormFactory.orderEntryList = function (params) {
var ENTRY_NOT_READY = 1
const SELECT_UPDATE_DELAY = 300
var msgPrefix = 'page.form.' + params.model + '.orderEntryList.'
const form = params.form
var container = params.orderEntryList.container
var attributes = {}
var entriesTemplate = $('._template_entries')
var round = Utils.moneyRound
//
// core funcs
//
var init = function () {
container.find('.entry_row').each(function (i, e) {
var row = $(e).nestedFieldSet()
if (gon.specific.order_locked) row.lockFromBillingChanges()
row.init()
row.hideContent()
if (row.isComplex()) row.showContent()
})
triggerListChanged()
updateSelects()
}
var updateIndexesTimeout
var updateIndexes = function () {
if (updateIndexesTimeout) clearTimeout(updateIndexesTimeout)
// massive complex addition calls updateIndex too many times and freezes ui
updateIndexesTimeout = setTimeout(function () {
container[0].querySelectorAll('.entry_row').forEach(function (e, i) {
var row = $(e).nestedFieldSet()
row.setIndex(i)
row.init()
})
}, 10)
}
var buildItem = function (proto) {
let rez = {
amount: proto.amount,
assistant_id: attributes.assistant_id,
client_id: attributes.client_id,
clinic_id: attributes.clinic_id,
date: attributes.date,
discount_disabled: proto.discount_disabled,
discount_percent: proto.discount_disabled
? 0
: (attributes.discount_percent || 0),
entry_type_id: proto.id,
final_price: proto.price,
kind: proto.kind,
number: proto.number,
price: proto.price,
state: ENTRY_NOT_READY,
store_id: attributes.store_id,
title: proto.title,
user_id: attributes.user_id,
referral_id: attributes.referral_id,
account: attributes.account
}
return rez
}
var triggerListChangedDelay = null
var triggerListChanged = function () {
if (triggerListChangedDelay) clearTimeout(triggerListChangedDelay)
triggerListChangedDelay = setTimeout(function () {
var result = {
sum: 0,
final_sum: 0,
discountable_sum: 0
}
var calculateDiscountableSum = function (elem) {
if (elem.isDeleted() || elem.get('discount_disabled', 'bool')) return
result.discountable_sum += elem.get('sum', 'float')
}
container.find('.entry_row').each(function (i, e) {
var row = $(e).nestedFieldSet()
if (row.isDeleted()) return
if (row.isComplex()) {
row.getDataArray('members').forEach(calculateDiscountableSum)
} else {
calculateDiscountableSum(row)
}
result.sum += parseFloat(row.get('sum'))
result.final_sum += parseFloat(row.get('final_sum'))
})
result.sum = round(result.sum)
result.final_sum = round(result.final_sum)
result.discount_sum = round(result.sum - result.final_sum)
PubSub.emit('page.form.' + params.model + '.updateComponents')
PubSub.emit(msgPrefix + 'listChanged', result)
}, 10)
}
// remove passed attributes & undefineds
var cleanAttributes = function (attrs) {
var omitList = Array.prototype.slice.call(arguments, 1)
attrs = _.omit.apply(_, [attrs].concat(omitList))
return _.omit(attrs, _.isUndefined)
}
const postAdd = () => {
updateIndexes()
triggerListChanged()
updateSelects()
}
var addEntry = function (entryType) {
var entryProto = $.extend({}, entryType, {amount: 1})
var entryAttributes = _.omit(buildItem(entryProto), _.isUndefined)
var entryHtml = entriesTemplate.railsTemplate('entries').html
var entryRow = entryHtml.first().nestedFieldSet()
entryRow.set(entryAttributes)
container.append(entryHtml)
entryProto.entry_type_members.forEach(function (member) {
addMember(member.member, {
complexRow: entryRow,
amount: member.amount,
skipInit: true
})
})
entryProto.entry_type_consumables.forEach(function (consumable) {
addConsumable(consumable.consumable, {
amount: consumable.amount,
entryRow: entryRow
})
})
entryRow.init()
entryRow.hideContent()
if (entryRow.isComplex()) entryRow.showContent()
entryRow.recalculate()
postAdd()
}
var addMember = function (entryType, params) {
var complexRow = params.complexRow
var memberHtml = complexRow
.getMemberTemplate()
.railsTemplate('members').html
var memberRow = memberHtml.first().nestedFieldSet()
var memberAmount = params.amount || 1
var memberAttributes = buildItem($.extend({}, entryType, {
amount: memberAmount
}))
memberAttributes = cleanAttributes(memberAttributes, 'order_id')
if (complexRow.get('discount_disabled', 'bool')) {
memberAttributes.discount_disabled = true
memberAttributes.discount_percent = 0
}
memberRow.set(memberAttributes)
complexRow.appendMemberHtml(memberHtml)
entryType.entry_type_consumables.forEach(function (consumable) {
addConsumable(consumable.consumable, {
amount: consumable.amount * memberAmount,
entryRow: memberRow
})
})
if (!params.skipInit) {
memberRow.hideContent()
complexRow.init()
postAdd()
}
}
var addConsumable = function (entryType, params) {
var entryRow = params.entryRow
var consumableHtml = entryRow
.getConsumableTemplate()
.railsTemplate('consumables').html
var consumableRow = consumableHtml.first().nestedFieldSet()
var consumableAttributes = buildItem($.extend({}, entryType, {
amount: params.amount || 1
}))
$.extend(consumableAttributes, {
price: 0,
final_price: 0,
sum: 0,
final_sum: 0,
discount_percent: 0,
discount_sum: 0,
consumable: true
})
consumableAttributes = cleanAttributes(consumableAttributes,
'order_id', 'user_id', 'assistant_id', 'referral_id'
)
consumableRow.set(consumableAttributes)
entryRow.appendConsumableHtml(consumableHtml)
entryRow.init()
entryRow.redraw()
consumableRow.redraw()
postAdd()
}
var checkFieldsForNull = function (opts) {
var attributes = {
'performer': 'user_id',
'assistant': 'assistant_id',
'referral': 'referral_id'
}
return container.find('.f-order-entry').toArray().some(function (e, i) {
var row = $(e).nestedFieldSet()
var currentAttrs = row.get()
return !currentAttrs[attributes[opts.change]]
})
}
var setAttributes = function (opts) {
container.find('.f-order-entry').each(function (i, e) {
var row = $(e).nestedFieldSet()
var newAttrs = _.clone(attributes)
if (row.isConsumable()) {
newAttrs = _.omit(newAttrs, 'user_id', 'assistant_id', 'referral_id')
}
var currentAttrs = row.get()
if (row.get('discount_disabled', 'bool')) delete newAttrs.discount_percent
if (row.get('price') == 0) delete newAttrs.discount_percent
if (currentAttrs.user_id && !(opts.change === 'performer')) delete newAttrs.user_id
if (currentAttrs.assistant_id && !(opts.change === 'assistant')) delete newAttrs.assistant_id
if (currentAttrs.referral_id && !(opts.change === 'referral')) delete newAttrs.referral_id
if (!currentAttrs.state && !(opts.change === 'state')) newAttrs.state = ENTRY_NOT_READY
if (!opts.force) {
if (currentAttrs.discount_percent && !(opts.change === 'discount')) delete newAttrs.discount_percent
}
row.set(newAttrs)
})
container.find('.entry_row').each(function (i, e) {
$(e).nestedFieldSet().recalculate()
})
triggerListChanged()
}
var openHiddenErrors = function (e, data) {
var entriesErrors = data.errors.entries_attributes
if (!entriesErrors) return
var entries = container.find('.entry_row').toArray().map(function (e) {
return $(e).nestedFieldSet()
})
Object.keys(entriesErrors).forEach(function (entryIndex) {
var entry = entries[parseInt(entryIndex)]
if (!entry) return
var entryErrors = entriesErrors[entryIndex]
var membersErrors = entryErrors.members_attributes
if (membersErrors) {
Object.keys(membersErrors).forEach(function (memberIndex) {
var member = entry.getDataArray('members')[parseInt(memberIndex)]
let memberErrors = membersErrors[memberIndex]
let consumableErrors = memberErrors.consumables_attributes
if (member && consumableErrors) member.showContent()
})
} else {
let consumableErrors = entryErrors.consumables_attributes
if (consumableErrors) entry.showContent()
}
})
}
//
// PubSub subscriptions
//
PubSub.on(msgPrefix + 'askAddEntry', function (msg, data) {
if (!data.selector) return addEntry(data.item)
var selectorRow = data.selector.closest('tr')
var complex = selectorRow.data('complex')
var entry = selectorRow.data('entry')
var type = selectorRow.data('type')
switch (type) {
case 'members':
addMember(data.item, {
amount: parseInt(complex.get('amount')),
complexRow: complex
})
break
case 'consumables':
addConsumable(data.item, {
amount: parseInt(entry.get('amount')),
entryRow: entry
})
break
default:
addEntry(data.item)
}
if (gon.application.use_tips_in_orders) { tipNotify(data.item) }
})
PubSub.on(msgPrefix + 'askSetAttributes', function (msg, data) {
attributes = $.extend(
attributes,
_.omit($.extend({}, data.attributes), _.isUndefined)
)
if (checkFieldsForNull(data) || !data.change) {
setAttributes({force: false})
} else {
bootbox.confirmYN(t('change_field') + ' ' + t(data.change) + ' ' + t('for_all_positions'), function (res) {
if (res) {
setAttributes({force: false, change: data.change})
}
})
}
})
PubSub.on(msgPrefix + 'askForceAttributes', function (msg, data) {
//debugger
attributes = $.extend(
attributes,
_.omit($.extend({}, data.attributes), _.isUndefined)
)
setAttributes({force: true})
})
PubSub.on('page.form.' + params.model + '.setNew', init)
PubSub.on('page.form.' + params.model + '.setEdit', init)
PubSub.on('page.form.' + params.model + '.submitError', openHiddenErrors)
//
// events
//
var recalculateSelector =
'.f-order-entry-price, .f-order-member-price, ' +
'.f-order-entry-amount, .f-order-member-amount, ' +
'.f-order-entry-discount-percent, .f-order-member-discount-percent, ' +
'.f-entry-referral'
container.on('keyup change mouseup', recalculateSelector, function () {
var row = $(this).closest('tr').nestedFieldSet()
if (row.getName() === 'members') {
row.data('complex').recalculate()
} else {
row.recalculate()
}
triggerListChanged()
})
container.on('keyup change mouseup', '.f-order-entry-amount', function () {
var row = $(this).closest('tr').nestedFieldSet()
if (row.getName() === 'members') row.data('complex').updateSchema()
if (row.getName() === 'consumables') row.data('entry').updateSchema()
row.updateContentItems()
triggerListChanged()
})
container.on('click', '.toggle-content-button', function () {
var row = $(this).closest('tr').nestedFieldSet()
if (row.enableConsumableSelector()) {
PubSub.emit('page.form.' + params.model + '.updateComponents')
}
row.toggleContent()
})
container.on('click', '.f-nested-destroy', function (e) {
e.preventDefault()
var row = $(this).closest('tr').nestedFieldSet()
var entry = row.data('entry')
if (entry) {
entry.getConsumableTemplate().railsTemplate('consumables', 'reduceIndex')
}
row.gracefulDestroy()
updateIndexes()
if (entry) entry.redraw()
triggerListChanged()
})
const updateSelects = () => {
const selector = '.f-entry-performer, .f-entry-assistant, .f-entry-referral'
const selectorStore = '.f-order-entry-store'
setTimeout(() => {
$(selector).select2({
dropdownAutoWidth: true,
templateSelection: Utils.userDropdownTemplate,
width: '100%'
})
}, SELECT_UPDATE_DELAY)
setTimeout(() => {
$(selectorStore).select2({
dropdownAutoWidth: true,
width: '100%'
})
}, SELECT_UPDATE_DELAY)
}
const buildReferralOption = (item) => {
const option = document.createElement('option')
option.text = item.short_name
option.value = item.id
return option
}
PubSub.on('page.form.order.referralField.newOption', (msg, item) => {
// add to all nodes
form.find('.f-entry-referral').each((i, select) => {
select.add(buildReferralOption(item), 0)
})
attributes = $.extend(
attributes,
{referral_id: item.id}
)
setAttributes({force: false})
// add to template
entriesTemplate[0]
.content.querySelector('.f-entry-referral')
.add(buildReferralOption(item), 0)
})
return {}
}
вот моя
запись отладки
--- !ruby/object:Entry
concise_attributes:
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: entry_type_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: created_at
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: updated_at
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: title
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: order_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: client_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: state
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: price
value_before_type_cast: '0.0'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: amount
value_before_type_cast: '1'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: stateful
value_before_type_cast: 'false'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: stackable
value_before_type_cast: 'true'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: clinic_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: pending_data
value_before_type_cast: 'false'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: sum
value_before_type_cast: '0.0'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: final_sum
value_before_type_cast: '0.0'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: discount_sum
value_before_type_cast: '0.0'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: discount_percent
value_before_type_cast: '0.0'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: user_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: store_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: kind
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: data
value_before_type_cast: ''
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: machine_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: complex_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: analysis_laboratory_id
- !ruby/object:ActiveRecord::Attribute::FromUser
name: deleted_at
original_attribute: !ruby/object:ActiveRecord::Attribute::FromDatabase
name: deleted_at
type: !ruby/object:ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
delegate_dc_obj: !ruby/object:ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime
precision:
scale:
limit:
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: created_by_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: updated_by_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: deleted_by_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: assistant_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: date
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: cost_price
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: be_result
value_before_type_cast: ''
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: comment
value_before_type_cast: ''
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: final_price
value_before_type_cast: '0.0'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: number
value_before_type_cast: ''
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: entry_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: consumable
value_before_type_cast: 'false'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: template_data
value_before_type_cast: ''
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: template_html
value_before_type_cast: ''
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: discount_disabled
value_before_type_cast: 'false'
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: is_protokol_save
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: referral_id
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: print_entry_info
value_before_type_cast: 'true'
new_record: true
active_record_yaml_version: 2
debug entry.title = nill, я не знаю почему
Можете ли вы объяснить, как формируется входное имя и как вставить в него атрибут title с его именем?