Skip to content

Commit f6ccb03

Browse files
FabPeteGerrit Code Review
authored andcommitted
Merge "[INTERNAL] ui.layout.Form: Aria role assignment"
2 parents ddba427 + 0b23f6e commit f6ccb03

File tree

6 files changed

+106
-13
lines changed

6 files changed

+106
-13
lines changed

src/sap.m/src/sap/m/TitlePropagationSupport.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@ sap.ui.define(["sap/ui/core/ControlBehavior"],
5252
* @version ${version}
5353
*
5454
* @param {string} sAggregationName the name of the aggregation which should be affected
55-
* @param {object} fnGetTitleID function that would return the ID of the title
55+
* @param {function} fnGetTitleInfo function that returns the title ID as a string,
56+
* or an object <code>{ id: string, role?: string }</code> containing the title ID and optionally the ARIA role
57+
* of the title's container
5658
*
5759
* @private
5860
* @alias sap.m.TitlePropagationSupport
5961
* @function
6062
*/
61-
return function (sAggregationName, fnGetTitleID) {
63+
return function (sAggregationName, fnGetTitleInfo) {
6264
// "this" is the prototype now when called with call()
6365

6466
// Ensure only Elements are enhanced
@@ -76,22 +78,32 @@ sap.ui.define(["sap/ui/core/ControlBehavior"],
7678

7779
var oAggregation = this.getMetadata().getAggregation(sAggregationName),
7880
aContent = oAggregation && oAggregation.get(this),
79-
sTitleID = fnGetTitleID && fnGetTitleID.call(this),
81+
vTitleInfo = fnGetTitleInfo && fnGetTitleInfo.call(this),
82+
oTitleData,
8083
oItem;
8184

8285
// Note: in case accessibility mode is off we don't need the propagation
83-
if (!ControlBehavior.isAccessibilityEnabled() || !sTitleID || !aContent
86+
if (!ControlBehavior.isAccessibilityEnabled() || !vTitleInfo || !aContent
8487
|| aContent.length === 0) {
8588
return false;
8689
}
8790

91+
// Normalize: string -> { id: string }, object stays as-is
92+
if (typeof vTitleInfo === "string") {
93+
oTitleData = { id: vTitleInfo };
94+
} else if (vTitleInfo.id) {
95+
oTitleData = vTitleInfo;
96+
} else {
97+
return false;
98+
}
99+
88100
// Propagate title ID only to first control in the content
89101
oItem = aContent[0];
90102
if (oItem && oItem._suggestTitleId && oItem.isA([
91103
"sap.ui.layout.form.SimpleForm",
92104
"sap.ui.layout.form.Form",
93105
"sap.ui.comp.smartform.SmartForm"])) {
94-
oItem._suggestTitleId(sTitleID);
106+
oItem._suggestTitleId(oTitleData);
95107
return true;
96108
}
97109
return false;

src/sap.m/test/sap/m/qunit/TitlePropagationSupport.qunit.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ sap.ui.define([
113113
if (!bNegative) {
114114
assert.strictEqual(oSuggestSpy.callCount, 1,
115115
"Suggest method is called once for control of type " + sType);
116-
assert.ok(oSuggestSpy.calledWith(sTitleID),
117-
"Suggest method on " + sType + " is called with the expected title ID: " +
116+
assert.ok(oSuggestSpy.calledWithMatch({id: sTitleID}),
117+
"Suggest method on " + sType + " is called with object containing the expected title ID: " +
118118
sTitleID);
119119
} else {
120120
assert.strictEqual(oSuggestSpy.callCount, 0,

src/sap.ui.layout/src/sap/ui/layout/form/Form.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,13 +356,20 @@ sap.ui.define([
356356
* Method used to propagate the <code>Title</code> control ID of a container control
357357
* (like a <code>Dialog</code> control) to use it as aria-label in the <code>Form</code>.
358358
* So the <code>Form</code> must not have an own title.
359-
* @param {string} sTitleID <code>Title</code> control ID
359+
* @param {string | object} sTitleID <code>Title</code> control ID or object <code>{ id: string, role?: string }</code> containing title ID and aria role
360360
* @private
361361
* @return {this} Reference to <code>this</code> to allow method chaining
362362
*/
363363
Form.prototype._suggestTitleId = function (sTitleID) {
364+
if (typeof sTitleID === "string") {
365+
this._sSuggestedTitleId = sTitleID;
366+
} else if (sTitleID && sTitleID.id) {
367+
this._sSuggestedTitleId = sTitleID.id;
368+
if (sTitleID.role) {
369+
this._sSuggestedTitleAriaRole = sTitleID.role;
370+
}
371+
}
364372

365-
this._sSuggestedTitleId = sTitleID;
366373
if (this.getDomRef()) {
367374
this.invalidate();
368375
}

src/sap.ui.layout/src/sap/ui/layout/form/FormRenderer.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ sap.ui.define([
3838
const oLayout = oForm.getLayout();
3939
const mAriaProps = {role: oLayout && oLayout.hasLabelledContainers(oForm) ? "region" : "form"};
4040

41+
const sTitleID = oLayout?.getRenderer().getTitleId(oForm) || oForm._sSuggestedTitleId;
42+
const bUsingSuggestedTitle = !oLayout?.getRenderer().getTitleId(oForm) && oForm._sSuggestedTitleId;
43+
44+
if (bUsingSuggestedTitle && oForm._sSuggestedTitleAriaRole === "region" && mAriaProps.role === "region") {
45+
// Do not render a role, if the title already defines a region and the form would do the same
46+
delete mAriaProps.role;
47+
}
48+
4149
// write only a DIV for the form and let the layout render the rest
4250
rm.openStart("div", oForm)
4351
.class("sapUiForm")
@@ -63,7 +71,6 @@ sap.ui.define([
6371
rm.attr('title', oForm.getTooltip_AsString());
6472
}
6573

66-
const sTitleID = oLayout?.getRenderer().getTitleId(oForm) || oForm._sSuggestedTitleId;
6774
if (sTitleID) {
6875
mAriaProps["labelledby"] = {value: sTitleID, append: true};
6976
}

src/sap.ui.layout/test/sap/ui/layout/qunit/form/Form.qunit.js

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ sap.ui.define([
2121
"sap/m/Title",
2222
"sap/m/Label",
2323
"sap/m/Input",
24-
"sap/ui/qunit/utils/nextUIUpdate"
24+
"sap/ui/qunit/utils/nextUIUpdate",
25+
"sap/uxap/ObjectPageLayout",
26+
"sap/uxap/ObjectPageSection",
27+
"sap/uxap/ObjectPageSubSection"
2528
],
2629
function(
2730
Element,
@@ -44,7 +47,10 @@ sap.ui.define([
4447
mTitle,
4548
Label,
4649
Input,
47-
nextUIUpdate
50+
nextUIUpdate,
51+
ObjectPageLayout,
52+
ObjectPageSection,
53+
ObjectPageSubSection
4854
) {
4955
"use strict";
5056

@@ -243,6 +249,16 @@ sap.ui.define([
243249
assert.equal(jQuery("#F1").attr("aria-labelledby"), "X T1", "aria-labelledby points to AriaLabel and Title");
244250
});
245251

252+
QUnit.test("_suggestTitleId with role: region", async function(assert) {
253+
// Setup: Form without title, receives suggested title with role "region" from parent
254+
oForm._suggestTitleId({ id: "parentTitle", role: "region" });
255+
await nextUIUpdate();
256+
257+
assert.equal(jQuery("#F1").attr("aria-labelledby"), "parentTitle", "aria-labelledby points to parent title");
258+
259+
assert.notEqual(jQuery("#F1").attr("role"), "region", "Form does not render role='region' when parent has region role");
260+
});
261+
246262
QUnit.module("FormContainer", {
247263
beforeEach: initTest,
248264
afterEach: afterTest
@@ -803,4 +819,51 @@ sap.ui.define([
803819
assert.notOk(oFormLayout.renderControlsForSemanticElement(), "no control rendering supported");
804820
});
805821

822+
QUnit.module("Form in ObjectPageSubSection", {
823+
beforeEach: async function() {
824+
oFormLayout = new FormLayout("FL1");
825+
// Create a FormContainer with a title so Form would render role="region"
826+
const oFormContainer = new FormContainer("FC1", {
827+
title: new Title("FCT1", { text: "Container Title" })
828+
});
829+
oForm = new Form("F1", {
830+
layout: oFormLayout,
831+
formContainers: [oFormContainer]
832+
});
833+
this.oObjectPage = new ObjectPageLayout("OP1", {
834+
sections: [new ObjectPageSection("OPS1", {
835+
title: "Section Title",
836+
subSections: [new ObjectPageSubSection("OPSS1", {
837+
title: "SubSection Title",
838+
blocks: [oForm]
839+
})]
840+
})]
841+
}).placeAt("qunit-fixture");
842+
await nextUIUpdate();
843+
},
844+
afterEach: function() {
845+
this.oObjectPage.destroy();
846+
oForm.destroy();
847+
oFormLayout.destroy();
848+
oForm = undefined;
849+
oFormLayout = undefined;
850+
}
851+
});
852+
853+
QUnit.test("Form with labelled container does not render duplicate region role", function(assert) {
854+
assert.ok(oFormLayout.hasLabelledContainers(oForm), "Form has labelled containers");
855+
856+
const sFormRole = jQuery("#F1").attr("role");
857+
assert.notEqual(sFormRole, "region", "Form does not have role='region' when inside ObjectPageSubSection");
858+
});
859+
860+
QUnit.test("Form with own title still renders region role", async function(assert) {
861+
const oTitle = new Title("FT1", { text: "Form Title" });
862+
oForm.setTitle(oTitle);
863+
await nextUIUpdate();
864+
865+
const sFormRole = jQuery("#F1").attr("role");
866+
assert.equal(sFormRole, "region", "Form has role='region' when it has its own title");
867+
});
868+
806869
});

src/sap.uxap/src/sap/uxap/ObjectPageSubSection.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,11 @@ sap.ui.define([
196196

197197
// Add Title Propagation Support
198198
TitlePropagationSupport.call(ObjectPageSubSection.prototype, "blocks", function () {
199-
return this._getTitleDomId();
199+
var sTitleDomId = this._getTitleDomId();
200+
if (!sTitleDomId) {
201+
return false;
202+
}
203+
return { id: sTitleDomId, role: "region" };
200204
});
201205

202206

0 commit comments

Comments
 (0)