decaf ValidatedMixin.coffee
test plan: - existing tests pass - substantial comments are preserved flag=none refs FOO-3470 Change-Id: I96eea89f542836f542b763de9cf5569bf2173086 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/316452 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Drake Harper <drake.harper@instructure.com> QA-Review: Drake Harper <drake.harper@instructure.com> Product-Review: Drake Harper <drake.harper@instructure.com>
This commit is contained in:
parent
cc4a09c195
commit
6d8d411d48
|
@ -1,125 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2013 - present 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/>.
|
||||
|
||||
import $ from 'jquery'
|
||||
import _ from 'underscore'
|
||||
import htmlEscape from 'html-escape'
|
||||
import '@canvas/util/toJSON'
|
||||
import '@canvas/jquery/jquery.disableWhileLoading'
|
||||
import '../../jquery/jquery.instructure_forms'
|
||||
import '@canvas/jquery/jquery.instructure_misc_helpers'
|
||||
|
||||
export default ValidatedMixin =
|
||||
|
||||
##
|
||||
# Errors are displayed relative to the field to which they belong. If
|
||||
# the key of the error in the response doesn't match the name attribute
|
||||
# of the form input element, configure a selector here.
|
||||
#
|
||||
# For example, given a form field like this:
|
||||
#
|
||||
# <input name="user[first_name]">
|
||||
#
|
||||
# and an error response like this:
|
||||
#
|
||||
# {errors: { first_name: {...} }}
|
||||
#
|
||||
# you would do this:
|
||||
#
|
||||
# fieldSelectors:
|
||||
# first_name: '[name=user[first_name]]'
|
||||
fieldSelectors: null
|
||||
|
||||
##
|
||||
# For a given dom element, retrieve the sibling tinymce wrapper.
|
||||
#
|
||||
# @param {jQuery Object} the textarea for whom we wish to get the
|
||||
# related tinymce editor area
|
||||
# @return {jQuery Object} the relevant div that wraps the tinymce
|
||||
# iframe related to this textarea
|
||||
findSiblingTinymce: ($el)->
|
||||
$el.siblings('.tox-tinymce').find(".tox-edit-area")
|
||||
|
||||
findField: (field, useGlobalSelector=false) ->
|
||||
selector = @fieldSelectors?[field] or "[name='#{field}']"
|
||||
if useGlobalSelector
|
||||
$el = $(selector)
|
||||
else
|
||||
$el = @$(selector)
|
||||
|
||||
if $el.data('rich_text')
|
||||
$el = @findSiblingTinymce($el)
|
||||
$el
|
||||
|
||||
##
|
||||
# Needs an errors object that looks like this:
|
||||
#
|
||||
# {
|
||||
# <field1>: [errors],
|
||||
# <field2>: [errors]
|
||||
# }
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# {
|
||||
# first_name: [
|
||||
# {
|
||||
# type: 'required'
|
||||
# message: 'First name is required'
|
||||
# },
|
||||
# {
|
||||
# type: 'no_numbers',
|
||||
# message: "First name can't contain numbers"
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
#
|
||||
# If globalSelector is true, it will look for this element everywhere
|
||||
# in the DOM instead of only `this` children elements. This is particularly
|
||||
# useful for some modals
|
||||
showErrors: (errors, useGlobalSelector=false) ->
|
||||
for fieldName, field of errors
|
||||
$input = field.element || @findField(fieldName, useGlobalSelector)
|
||||
html = field.message || (htmlEscape(@translations?[message] or message) for {message} in field).join('</p><p>')
|
||||
$input.errorBox($.raw("#{html}"))?.css("z-index", "1100")?.attr('role', 'alert')
|
||||
@attachErrorDescription($input, html)
|
||||
field.$input = $input
|
||||
field.$errorBox = $input.data 'associated_error_box'
|
||||
|
||||
attachErrorDescription: ($input, message) ->
|
||||
errorDescriptionField = @findOrCreateDescriptionField($input)
|
||||
errorDescriptionField["description"].text($.raw("#{message}"))
|
||||
$input.attr('aria-describedby',
|
||||
errorDescriptionField["description"].attr('id') + " " +
|
||||
errorDescriptionField["originalDescriptionIds"]
|
||||
)
|
||||
|
||||
findOrCreateDescriptionField: ($input) ->
|
||||
id = $input.attr('id')
|
||||
unless $("##{id}_sr_description").length > 0
|
||||
$('<div>').attr({
|
||||
id: "#{id}_sr_description"
|
||||
class: "screenreader-only"
|
||||
}).insertBefore($input)
|
||||
description = $("##{id}_sr_description")
|
||||
originalDescriptionIds = @getExistingDescriptionIds($input, id)
|
||||
{description: description, originalDescriptionIds: originalDescriptionIds}
|
||||
|
||||
getExistingDescriptionIds: ($input, id) ->
|
||||
descriptionIds = $input.attr('aria-describedby')
|
||||
idArray = if descriptionIds then descriptionIds.split(" ") else []
|
||||
_.without(idArray,"#{id}_sr_description")
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - present 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/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-void */
|
||||
|
||||
import $ from 'jquery'
|
||||
import _ from 'underscore'
|
||||
import htmlEscape from 'html-escape'
|
||||
import '@canvas/util/toJSON'
|
||||
import '@canvas/jquery/jquery.disableWhileLoading'
|
||||
import '../../jquery/jquery.instructure_forms'
|
||||
import '@canvas/jquery/jquery.instructure_misc_helpers'
|
||||
|
||||
export default {
|
||||
// Errors are displayed relative to the field to which they belong. If
|
||||
// the key of the error in the response doesn't match the name attribute
|
||||
// of the form input element, configure a selector here.
|
||||
//
|
||||
// For example, given a form field like this:
|
||||
//
|
||||
// <input name="user[first_name]">
|
||||
//
|
||||
// and an error response like this:
|
||||
//
|
||||
// {errors: { first_name: {...} }}
|
||||
//
|
||||
// you would do this:
|
||||
//
|
||||
// fieldSelectors:
|
||||
// first_name: '[name=user[first_name]]'
|
||||
fieldSelectors: null,
|
||||
// For a given dom element, retrieve the sibling tinymce wrapper.
|
||||
//
|
||||
// @param {jQuery Object} the textarea for whom we wish to get the
|
||||
// related tinymce editor area
|
||||
// @return {jQuery Object} the relevant div that wraps the tinymce
|
||||
// iframe related to this textarea
|
||||
findSiblingTinymce($el) {
|
||||
return $el.siblings('.tox-tinymce').find('.tox-edit-area')
|
||||
},
|
||||
findField(field, useGlobalSelector) {
|
||||
let $el, ref
|
||||
if (useGlobalSelector == null) {
|
||||
useGlobalSelector = false
|
||||
}
|
||||
const selector =
|
||||
((ref = this.fieldSelectors) != null ? ref[field] : void 0) || "[name='" + field + "']"
|
||||
if (useGlobalSelector) {
|
||||
$el = $(selector)
|
||||
} else {
|
||||
$el = this.$(selector)
|
||||
}
|
||||
if ($el.data('rich_text')) {
|
||||
$el = this.findSiblingTinymce($el)
|
||||
}
|
||||
return $el
|
||||
},
|
||||
// Needs an errors object that looks like this:
|
||||
//
|
||||
// {
|
||||
// <field1>: [errors],
|
||||
// <field2>: [errors]
|
||||
// }
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// {
|
||||
// first_name: [
|
||||
// {
|
||||
// type: 'required'
|
||||
// message: 'First name is required'
|
||||
// },
|
||||
// {
|
||||
// type: 'no_numbers',
|
||||
// message: "First name can't contain numbers"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// If globalSelector is true, it will look for this element everywhere
|
||||
// in the DOM instead of only `this` children elements. This is particularly
|
||||
// useful for some modals
|
||||
showErrors(errors, useGlobalSelector) {
|
||||
let $input, field, fieldName, html, message, ref, ref1
|
||||
if (useGlobalSelector == null) {
|
||||
useGlobalSelector = false
|
||||
}
|
||||
const results = []
|
||||
for (fieldName in errors) {
|
||||
field = errors[fieldName]
|
||||
$input = field.element || this.findField(fieldName, useGlobalSelector)
|
||||
html =
|
||||
field.message ||
|
||||
// eslint-disable-next-line no-loop-func
|
||||
function () {
|
||||
let i, len, ref_
|
||||
const results1 = []
|
||||
for (i = 0, len = field.length; i < len; i++) {
|
||||
message = field[i].message
|
||||
results1.push(
|
||||
htmlEscape(((ref_ = this.translations) != null ? ref_[message] : void 0) || message)
|
||||
)
|
||||
}
|
||||
return results1
|
||||
}
|
||||
.call(this)
|
||||
.join('</p><p>')
|
||||
if ((ref = $input.errorBox($.raw('' + html))) != null) {
|
||||
if ((ref1 = ref.css('z-index', '1100')) != null) {
|
||||
ref1.attr('role', 'alert')
|
||||
}
|
||||
}
|
||||
this.attachErrorDescription($input, html)
|
||||
field.$input = $input
|
||||
results.push((field.$errorBox = $input.data('associated_error_box')))
|
||||
}
|
||||
return results
|
||||
},
|
||||
attachErrorDescription($input, message) {
|
||||
const errorDescriptionField = this.findOrCreateDescriptionField($input)
|
||||
errorDescriptionField.description.text($.raw('' + message))
|
||||
return $input.attr(
|
||||
'aria-describedby',
|
||||
errorDescriptionField.description.attr('id') +
|
||||
' ' +
|
||||
errorDescriptionField.originalDescriptionIds
|
||||
)
|
||||
},
|
||||
findOrCreateDescriptionField($input) {
|
||||
const id = $input.attr('id')
|
||||
if (!($('#' + id + '_sr_description').length > 0)) {
|
||||
$('<div>')
|
||||
.attr({
|
||||
id: id + '_sr_description',
|
||||
class: 'screenreader-only',
|
||||
})
|
||||
.insertBefore($input)
|
||||
}
|
||||
const description = $('#' + id + '_sr_description')
|
||||
const originalDescriptionIds = this.getExistingDescriptionIds($input, id)
|
||||
return {
|
||||
description,
|
||||
originalDescriptionIds,
|
||||
}
|
||||
},
|
||||
getExistingDescriptionIds($input, id) {
|
||||
const descriptionIds = $input.attr('aria-describedby')
|
||||
const idArray = descriptionIds ? descriptionIds.split(' ') : []
|
||||
return _.without(idArray, id + '_sr_description')
|
||||
},
|
||||
}
|
Loading…
Reference in New Issue