manage focus when deleting a link from profile

also do some minor refactoring around this coffeescript to make it safer
and easier to test.

fixes CNVS-26490

test plan:
- basic regression test around user profile editing
  (you need to enable profiles in account settings)
- specifically, when you delete links while editing the profile, focus
  should go to the previous delete link button or the bio textarea (if
  you delete the top one)

Change-Id: I15b5e552485c447d51cdccedf9990456de10b13d
Reviewed-on: https://gerrit.instructure.com/71298
Tested-by: Jenkins
Reviewed-by: Ryan Shaw <ryan@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Product-Review: Simon Williams <simon@instructure.com>
This commit is contained in:
Simon Williams 2016-02-02 11:44:47 -07:00
parent 22d80f8698
commit 772cffbae6
5 changed files with 149 additions and 99 deletions

View File

@ -17,103 +17,7 @@
#
require [
'i18n!user_profile',
'Backbone'
'jquery'
'str/htmlEscape'
'compiled/util/AvatarWidget'
'compiled/tinymce'
'jquery.instructure_forms'
'tinymce.editor_box'
], (I18n, {View}, $, htmlEscape, AvatarWidget) ->
class ProfileShow extends View
el: document.body
events:
'click [data-event]': 'handleDeclarativeClick'
'submit #edit_profile_form': 'validateForm'
'click .report_avatar_link': 'reportAvatarLink'
attemptedDependencyLoads: 0
initialize: ->
super
new AvatarWidget('.profile-link')
reportAvatarLink: (e) ->
e.preventDefault()
return if !confirm(I18n.t("Are you sure you want to report this profile picture?"))
link = $(e.currentTarget)
$('.avatar').hide()
$.ajaxJSON(link.attr('href'), "POST", {}, (data) =>
$.flashMessage I18n.t("The profile picture has been reported")
)
handleDeclarativeClick: (event) ->
event.preventDefault()
$target = $ event.currentTarget
method = $target.data 'event'
@[method]? event, $target
##
# first run initializes some stuff, then is reassigned
# to a showEditForm
editProfile: ->
@initEdit()
@editProfile = @showEditForm
showEditForm: ->
@$el.addClass('editing').removeClass('not-editing')
@$('.profile_links').removeClass('span6')
initEdit: ->
if @options.links?.length
@addLinkField(null, null, title, url) for {title, url} in @options.links
else
@addLinkField()
@addLinkField()
# setTimeout so tiny has some width to read
#setTimeout -> @$('#profile_bio').editorBox()
@showEditForm()
cancelEditProfile: ->
@$el.addClass('not-editing').removeClass('editing')
@$('.profile_links').addClass('span6')
##
# Event handler that can also be called manually.
# When called manually, it will focus the first input in the new row
addLinkField: (event, $el, title = '', url = '') ->
@$linkFields ?= @$ '#profile_link_fields'
$row = $ """
<tr>
<td><input aria-label="#{htmlEscape I18n.t("Link title")}" type="text" maxlength="255" name="link_titles[]" value="#{htmlEscape title}"></td>
<td></td>
<td><input aria-label="#{htmlEscape I18n.t("Link Url")}" type="text" name="link_urls[]" value="#{htmlEscape url}"></td>
<td><a href="#" data-event="removeLinkRow"><span class="screenreader-only">#{htmlEscape I18n.t("Remove")}</span><i class="icon-end"></i></a></td>
</tr>
"""
@$linkFields.append $row
# focus if called from the "add row" button
if event?
event.preventDefault()
$row.find('input:first').focus()
removeLinkRow: (event, $el) ->
$el.parents('tr').remove()
validateForm: (event) ->
validations =
property_validations:
'user_profile[title]': (value) ->
if value && value.length > 255
return I18n.t("profile_title_too_long", "Title is too long")
if !$(event.target).validateForm(validations)
event.preventDefault()
'compiled/views/profiles/ProfileShow'
], (ProfileShow) ->
new ProfileShow ENV.PROFILE

View File

@ -0,0 +1,115 @@
#
# Copyright (C) 2016 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
define [
'i18n!user_profile',
'Backbone'
'jquery'
'jst/profiles/addLinkRow'
'compiled/util/AvatarWidget'
'compiled/tinymce'
'jquery.instructure_forms'
'tinymce.editor_box'
], (I18n, Backbone, $, addLinkRow, AvatarWidget) ->
class ProfileShow extends Backbone.View
el: document.body
events:
'click [data-event]': 'handleDeclarativeClick'
'submit #edit_profile_form': 'validateForm'
'click .report_avatar_link': 'reportAvatarLink'
attemptedDependencyLoads: 0
initialize: ->
super
new AvatarWidget('.profile-link')
reportAvatarLink: (e) ->
e.preventDefault()
return if !confirm(I18n.t("Are you sure you want to report this profile picture?"))
link = $(e.currentTarget)
$('.avatar').hide()
$.ajaxJSON(link.attr('href'), "POST", {}, (data) =>
$.flashMessage I18n.t("The profile picture has been reported")
)
handleDeclarativeClick: (event) ->
event.preventDefault()
$target = $ event.currentTarget
method = $target.data 'event'
@[method]? event, $target
##
# first run initializes some stuff, then is reassigned
# to a showEditForm
editProfile: ->
@initEdit()
@editProfile = @showEditForm
showEditForm: ->
@$el.addClass('editing').removeClass('not-editing')
@$('.profile_links').removeClass('span6')
initEdit: ->
if @options.links?.length
@addLinkField(null, null, title, url) for {title, url} in @options.links
else
@addLinkField()
@addLinkField()
# setTimeout so tiny has some width to read
#setTimeout -> @$('#profile_bio').editorBox()
@showEditForm()
cancelEditProfile: ->
@$el.addClass('not-editing').removeClass('editing')
@$('.profile_links').addClass('span6')
##
# Event handler that can also be called manually.
# When called manually, it will focus the first input in the new row
addLinkField: (event, $el, title = '', url = '') ->
@$linkFields ?= @$ '#profile_link_fields'
$row = $(addLinkRow({title: title, url: url}))
@$linkFields.append $row
# focus if called from the "add row" button
if event?
event.preventDefault()
$row.find('input:first').focus()
removeLinkRow: (event, $el) ->
$parentRow = $el.parents('tr')
$toFocus = $parentRow.prev().find('.remove_link_row')
if $toFocus.length == 0
$toFocus = $('#profile_bio')
$parentRow.remove()
$toFocus.focus()
validateForm: (event) ->
validations =
property_validations:
'user_profile[title]': (value) ->
if value && value.length > 255
return I18n.t("profile_title_too_long", "Title is too long")
if !$(event.target).validateForm(validations)
event.preventDefault()

View File

@ -0,0 +1,6 @@
<tr>
<td><input aria-label="{{t "Link title" }}" type="text" maxlength="255" name="link_titles[]" value="{{title}}"></td>
<td>→</td>
<td><input aria-label="{{t "Link Url" }}" type="text" name="link_urls[]" value="{{url}}"></td>
<td><a href="#" class="remove_link_row" data-event="removeLinkRow"><span class="screenreader-only">{{t "Remove" }}</span><i class="icon-end"></i></a></td>
</tr>

View File

@ -151,4 +151,3 @@
<% end %>
</div>
<% end %>

View File

@ -0,0 +1,26 @@
define [
'jquery'
'compiled/views/profiles/ProfileShow'
], ($, ProfileShow) ->
module 'ProfileShow',
setup: ->
@fixtures = document.getElementById('fixtures')
@fixtures.innerHTML = "<div class='.profile-link'></div>"
@fixtures.innerHTML += "<textarea id='profile_bio'></textarea>"
@fixtures.innerHTML += "<table id='profile_link_fields'></table>"
teardown: ->
@fixtures.innerHTML = ""
test 'manages focus on link removal', ->
@view = new ProfileShow
@view.addLinkField()
$row1 = $('#profile_link_fields tr:last-child')
@view.addLinkField()
$row2 = $('#profile_link_fields tr:last-child')
@view.removeLinkRow(null, $row2.find('.remove_link_row'))
equal document.activeElement, $row1.find('.remove_link_row')[0]
@view.removeLinkRow(null, $row1.find('.remove_link_row'))
equal document.activeElement, $('#profile_bio')[0]