Here are the files.
class Person < Vue
def initialize
@committer = {}
@response = nil
end
def render
# usage information for authenticated users (owner, secretary, etc.)
if @auth
_div.alert.alert_success 'Double click on a field in this color to edit.'
end
_h2 "#{@committer.id}@apache.org"
# Name
_PersonName person: self, edit: @edit
_div.row do
_div.name 'LDAP Create Date'
_div.value do
_ @committer.createTimestamp
end
end
# Personal URL
if @committer.urls || @auth
@committer.urls ||= []
_PersonUrls person: self, edit: @edit
end
# PMCs
noPMCsub = false
pmcs = @committer.pmcs
unless pmcs.empty?
_div.row do
_div.name 'PMCs'
_div.value do
_ul pmcs do |pmc|
_li {
_a pmc, href: "committee/#{pmc}"
if @committer.privateNosub
if @committer.privateNosub.include? pmc
noPMCsub = true
_b " (*)"
end
end
if @committer.chairOf.include? pmc
_ ' (chair)'
end
unless @committer.committees.include?(pmc)
_b ' (not in LDAP committee group)'
end
}
end
if noPMCsub
_br
_p {
_ '(*) could not find a subscription to the private@ mailing list for this PMC'
_br
_ 'Perhaps the subscription address is not listed in the LDAP record'
_br
_ '(Note that digest subscriptions are not currently included)'
}
end
end
end
end
# Committees
missingPMCs = false
committees = @committer.committees
unless committees.empty?
_div.row do
_div.name 'Committees'
_div.value do
noPMCsub = false
_ul committees do |pmc|
next if @committer.pmcs.include? pmc
missingPMCs = true
_li {
_a pmc, href: "committee/#{pmc}"
if @committer.chairOf.include? pmc
_ ' (chair)'
end
}
end
if missingPMCs
_ 'In LDAP committee group, but not on the corresponding PMC'
else
_ '(excludes PMCs listed above)'
end
end
end
end
# Committer
commit_list = @committer.committer
unless commit_list.all? {|pmc| committees.include? pmc}
_div.row do
_div.name 'Committer'
_div.value do
_ul commit_list do |pmc|
next if committees.include? pmc
_li {_a pmc, href: "committee/#{pmc}"}
end
end
end
end
# Groups
unless @committer.groups.empty?
_div.row do
_div.name 'Groups'
_div.value do
_ul @committer.groups do |group|
next if group == 'apldap'
if group == 'committers'
_li {_a group, href: "committer/"}
elsif group == 'member'
_li {_a group, href: "members"}
else
_li {_a group, href: "group/#{group}"}
end
if @committer.chairOf.length > 0 and not @committer.groups.include? 'pmc-chairs'
_ '[Missing: pmc-chairs]'
end
end
end
end
end
# Podlings
unless @committer.podlings.empty?
_div.row do
_div.name 'Podlings'
_div.value do
_ul @committer.podlings do |podlings|
_li {_a podlings, href: "ppmc/#{podlings}"}
end
end
end
end
# Non-PMCs
nonpmcs = @committer.nonpmcs
unless nonpmcs.empty?
_div.row do
_div.name 'non-PMCs'
_div.value do
_ul nonpmcs do |nonpmc|
_li {
_a nonpmc, href: "nonpmc/#{nonpmc}"
if @committer.nonPMCchairOf.include? nonpmc
_ ' (chair)'
end
}
end
end
end
end
# Email addresses
# always present
_PersonEmailForwards person: self, edit: @edit
#Â always present (even if an empty array)
_PersonEmailAlt person: self, edit: @edit
if @committer.email_other
_PersonEmailOther person: self # not editable
end
# Moderates
if @committer.moderates and @committer.moderates.keys().length > 0
_div.row do
_div.name 'Moderates'
_div.value do
_ul @committer.moderates.keys() do |list_name|
_li do
_a list_name, href: 'https://lists.apache.org/list.html?' +
list_name
_span " as "
_span @committer.moderates[list_name].join(', ')
end
end
_ "(last checked #{@committer.modtime})"
end
end
end
# subscriptions
if @committer.subscriptions
_div.row do
_div.name 'Subscriptions'
_div.value do
_ul @committer.subscriptions do |list_email|
_li do
_a list_email[0],
href: 'https://lists.apache.org/list.html?' + list_email[0]
_span " as "
_span list_email[1]
end
end
_ "(last checked #{@committer.subtime})"
end
end
end
# digests
if @committer.digests
_div.row do
_div.name 'Digest Subscriptions'
_div.value do
_ul @committer.digests do |list_email|
_li do
_a list_email[0],
href: 'https://lists.apache.org/list.html?' + list_email[0]
_span " as "
_span list_email[1]
end
end
_ "(last checked #{@committer.digtime})"
end
end
end
# PGP keys
if @committer.pgp || @auth
@committer.pgp ||= []
_PersonPgpKeys person: self, edit: @edit
end
# hosts
_div.row do
_div.name 'Host Access'
_div.value do
# pre avoids wrapping on hyphens and reduces number of lines on the page
_pre @committer.host.join(' ')
end
end
if @committer.inactive
_div.row do
_div.name 'Inactive (cannot login)'
_div.value @committer.inactive
end
end
# SSH keys
if @committer.ssh || @auth
@committer.ssh ||= []
_PersonSshKeys person: self, edit: @edit
end
# GitHub username
if @committer.githubUsername || @auth
@committer.githubUsername ||= []
_PersonGitHub person: self, edit: @edit
end
if @committer.member
_PersonMemberStatus person: self, edit: @edit
# Members.txt
if @committer.member.info
_PersonMemberText person: self, edit: @edit
end
if @committer.member.nomination
_div.row do
_div.name 'Nomination'
_div.value {_pre @committer.member.nomination}
end
end
end
# Forms on file (only present if env.user is a member)
if @committer.forms
_PersonForms person: self
end
# SpamAssassin score
_PersonSascore person: self, edit: @edit
# modal dialog for dry run results and errors
_div.modal.fade.wide_form tabindex: -1 do
_div.modal_dialog do
_div.modal_content do
_div.modal_header do
_button.close 'x', data_dismiss: 'modal'
_h4 @response_title
end
_div.modal_body do
_textarea value: @response, readonly: true
end
_div.modal_footer do
_button.btn.btn_default 'Close', data_dismiss: 'modal'
end
end
end
end
end
# initialize committer, determine if user is authorized to make
# changes, map Vue model to React model
def created()
@committer = @@committer
@auth = (@@auth.id == @@committer.id or @@auth.secretary or @@auth.root)
# map Vue model to React model
self.state = self['$data']
self.props = self['$props']
end
# on initial display, look for add editable rows, highlight them,
# and watch for double clicks on them
def mounted()
return unless @auth
Array(document.querySelectorAll('div.row[data-edit]')).each do |div|
div.addEventListener('dblclick', self.dblclick)
div.querySelector('div.name').classList.add 'bg-success'
console.log('mounted div: ' + div)
console.log('div.dataset.edit: ' + div.dataset.edit)
end
end
# when a double click occurs, toggle the associated state
def dblclick(event)
row = event.currentTarget
console.log(' dblclick event.currentTarget: ' + row)
console.log(' dblclick event.currentTarget.dataset: ' + row.dataset)
console.log(' dblclick event.currentTarget.dataset.edit: ' + row.dataset.edit)
if row.dataset.edit == @edit
@edit = nil
else
@edit = row.dataset.edit
end
window.getSelection().removeAllRanges()
end
# after update, register event listeners on forms
def updated()
Array(document.querySelectorAll('div[data-edit]')).each do |tr|
form = tr.querySelector('form')
if form
form.setAttribute 'data-action', tr.getAttribute('data-edit')
jQuery('input[type=submit],button', form).click(self.submit)
end
end
end
# submit form using AJAX
def submit(event)
event.preventDefault()
form = jQuery(event.currentTarget).closest('form')
target = event.target
parent = event.currentTarget
memstatElement = target.closest('.row')
# if button is cancel, don't submit but remove @edit form buttons
console.log(' submit event: ' + event)
console.log(' submit target: ' + target)
console.log(' submit parent: ' + parent)
console.log(' submit parent.getAttribute(data_cancel): ' + parent.getAttribute('data_cancel'))
console.log(' submit target.dataset_edit: ' + target.dataset_edit)
console.log(' submit target.dataset: ' + target.dataset)
console.log(' submit target.dataset.edit: ' + target.dataset.edit)
console.log(' submit memstatElement.dataset.edit: ' + memstatElement.dataset.edit)
cancel_submit = target.getAttribute('data_cancel')
console.log(' submit cancel_submit: ' + cancel_submit)
if cancel_submit
memstatElement.dataset.edit = nil
@edit = nil
return
end
# serialize form
formData = form.serializeArray();
# add button if it has a value
if target and target.getAttribute('name') and target.getAttribute('value')
formData.push name: target.getAttribute('name'),
value: target.getAttribute('value')
end
# indicate dryrun is requested if option or control key is down
if event.altKey or event.ctrlKey
formData.unshift name: 'dryrun', value: true
end
# issue request
jQuery.ajax(
method: (form[0].method || 'GET').upcase(),
url: document.location.href + '/' + form[0].getAttribute('data-action'),
data: formData,
dataType: 'json',
success: ->(response) {
@committer = response.committer if response.committer
# turn off edit mode on this field
row = form.closest('.row')[0]
@edit = nil if row and row.dataset.edit == @edit
},
error: ->(response) {
json = response.responseJSON
if json.exception
@response_title = json.exception
@response = JSON.stringify(json, nil, 2)
jQuery('div.modal').modal('show')
else
alert response.statusText
end
},
complete: ->(response) do
json = response.responseJSON
# show results of dryrun
if formData[0] and formData[0].name == 'dryrun'
@response_title = 'Dry run results'
@response = JSON.stringify(json, nil, 2)
jQuery('div.modal').modal('show')
end
if json.error
@response_title = 'Error occurred'
@response = JSON.stringify(json, nil, 2)
jQuery('div.modal').modal('show')
end
# reenable form for later reuse
jQuery('input, button', form).prop('disabled', false)
end
)
# disable input
jQuery('input, button', form).prop('disabled', true)
end
end
#
# Render and edit a person's member status
#
class PersonMemberStatus < Vue
def render
committer = @@person.state.committer
owner = @@person.props.auth.id == committer.id
_div.row data_edit: ('memstat' if @@person.props.auth.secretary or owner) do
_div.name 'Member status'
if committer.member.status
_div.value do
_span committer.member.status
def canx(event)
console.log('canx called from Cancel button')
parent = event.currentTarget
memstatElement = parent.closest('.row')
console.log('event: ' + event + ' parent: ' + parent)
console.log('parent: ' + parent + ' memstatElement: ' + memstatElement)
console.log('preventing default')
console.log('memstatElement.dataset: ' +memstatElement.dataset)
console.log('memstatElement.dataset.edit: ' + memstatElement.dataset.edit)
#event.preventDefault()
#memstatElement.dataset.edit = nil
end
if @@edit == :memstat
opt = { year: 'numeric', month: 'long' } # Suggested date
dod = Date.new.toLocaleDateString('en-US', opt)
_form.inline method: 'post' do
# Cancel this form (implemented in main.js submit(event)
_button.btn.btn_secondary 'Cancel', data_cancel:true, onClick:canx
# These actions are only for the person's own use
if (owner)
if committer.member.status.include? 'Active'
if committer.forms['emeritus_request']
_button.btn.btn_primary 'rescind emeritus request',
name: 'action', value: 'rescind_emeritus'
else
_button.btn.btn_primary 'request emeritus status',
name: 'action', value: 'request_emeritus'
end
end
end
# These actions are only for secretary's use
if (@@person.props.auth.secretary)
if committer.member.status.include? 'Active'
_button.btn.btn_primary 'move to emeritus',
name: 'action', value: 'emeritus'
_button.btn.btn_primary 'move to deceased',
name: 'action', value: 'deceased'
_input 'dod', name: 'dod', value: dod
elsif committer.member.status.include? 'Emeritus'
_button.btn.btn_primary 'move to active',
name: 'action', value: 'active'
_button.btn.btn_primary 'move to deceased',
name: 'action', value: 'deceased'
_input 'dod', name: 'dod', value: dod
elsif committer.member.status.include? 'Deceased'
_button.btn.btn_primary 'move to active',
name: 'action', value: 'active'
_button.btn.btn_primary 'move to emeritus',
name: 'action', value: 'emeritus'
end
end
end
end
end
end
end
end
end
And the console logs:
[Log] dblclick event.currentTarget: [object HTMLDivElement] (app.js, line 3268)
[Log] dblclick event.currentTarget.dataset: [object DOMStringMap] (app.js,
line 3269)
[Log] dblclick event.currentTarget.dataset.edit: memstat (app.js, line 3270)
[Log] canx called from Cancel button (app.js, line 4021)
[Log] event: [object MouseEvent] parent: [object HTMLButtonElement] (app.js,
line 4024)
[Log] parent: [object HTMLButtonElement] memstatElement: [object
HTMLDivElement] (app.js, line 4025)
[Log] preventing default (app.js, line 4026)
[Log] memstatElement.dataset: [object DOMStringMap] (app.js, line 4027)
[Log] memstatElement.dataset.edit: memstat (app.js, line 4028)
[Log] submit event: [object Object] (app.js, line 3291)
[Log] submit target: [object HTMLButtonElement] (app.js, line 3292)
[Log] submit parent: [object HTMLButtonElement] (app.js, line 3293)
[Log] submit parent.getAttribute(data_cancel): null (app.js, line 3294)
[Log] submit target.dataset_edit: undefined (app.js, line 3295)
[Log] submit target.dataset: [object DOMStringMap] (app.js, line 3296)
[Log] submit target.dataset.edit: undefined (app.js, line 3297)
[Log] submit memstatElement.dataset.edit: memstat (app.js, line 3298)
[Log] submit cancel_submit: null (app.js, line 3300)
> On May 26, 2020, at 2:19 PM, Sam Ruby <[email protected]> wrote:
>
> On Tue, May 26, 2020 at 1:32 AM Craig Russell <[email protected]> wrote:
>>
>> But calling preventDefault on the event doesn't appear to do anything. It
>> still calls the POST behavior, and does not cause the inline edit menu to
>> disappear. And setting the edit function to nil doesn't do anything either.
>>
>> Maybe there is something else that I need to do?
>
> I'll admit that I'm not clear on what you are trying to accomplish,
> but apparently the problem here is that there is another piece of code
> that attaches an event handler (and calls preventDefault):
>
> https://github.com/apache/whimsy/blob/d3246f107a35f4d989350f4c1ca64366c98ef423/www/roster/views/person/main.js.rb#L341
>
> Perhaps it would be best to add an attribute to the button, and have
> the submit method remove the buttons and exit early:
>
> diff --git a/www/roster/views/person/main.js.rb
> b/www/roster/views/person/main.js.rb
> index 1b5ffdb8..f498489f 100644
> --- a/www/roster/views/person/main.js.rb
> +++ b/www/roster/views/person/main.js.rb
> @@ -355,6 +355,12 @@ class Person < Vue
> form = jQuery(event.currentTarget).closest('form')
> target = event.target
>
> + # if button is a cancel button, don't submit and remove buttons
> + if target.getAttribute('data-cancel')
> + @edit = null
> + return
> + end
> +
> # serialize form
> formData = form.serializeArray();
>
> diff --git a/www/roster/views/person/memstat.js.rb
> b/www/roster/views/person/memstat.js.rb
> index 39367c40..e8d3d318 100644
> --- a/www/roster/views/person/memstat.js.rb
> +++ b/www/roster/views/person/memstat.js.rb
> @@ -35,6 +35,8 @@ class PersonMemberStatus < Vue
> _button.btn.btn_primary 'move to emeritus',
> name: 'action', value: 'emeritus'
> end
> +
> + _button.btn.btn_secondary 'cancel', data_cancel: true
> end
> end
> end
>
> - Sam Ruby
Craig L Russell
[email protected]