From d500d1fd08237ae64e05bb9c060c1386316d0283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 21 Mar 2022 15:58:44 +0100 Subject: [PATCH] MOBILE-3814 dom: Improve element position --- .../ddimageortext/classes/ddimageortext.ts | 29 +++++--- src/addons/qtype/ddmarker/classes/ddmarker.ts | 54 ++++++++------- .../qtype/ddmarker/classes/graphics_api.ts | 20 ++---- src/addons/qtype/ddwtos/classes/ddwtos.ts | 40 ++++++----- src/core/services/utils/dom.ts | 69 +++++++++---------- 5 files changed, 111 insertions(+), 101 deletions(-) diff --git a/src/addons/qtype/ddimageortext/classes/ddimageortext.ts b/src/addons/qtype/ddimageortext/classes/ddimageortext.ts index 3d066d1f0..bdebeaabf 100644 --- a/src/addons/qtype/ddimageortext/classes/ddimageortext.ts +++ b/src/addons/qtype/ddimageortext/classes/ddimageortext.ts @@ -78,13 +78,18 @@ export class AddonQtypeDdImageOrTextQuestion { return bgImgXY; } - const position = CoreDomUtils.getElementXY(bgImg, undefined, 'ddarea'); + const ddArea = this.container.querySelector('.ddarea'); + if (!ddArea) { + return bgImgXY; + } + + const position = CoreDomUtils.getRelativeElementPosition(bgImg, ddArea); // Render the position related to the current image dimensions. bgImgXY[0] *= this.proportion; bgImgXY[1] *= this.proportion; - return [Number(bgImgXY[0]) + position[0] + 1, Number(bgImgXY[1]) + position[1] + 1]; + return [bgImgXY[0] + position.x + 1, bgImgXY[1] + position.y + 1]; } /** @@ -409,10 +414,15 @@ export class AddonQtypeDdImageOrTextQuestion { } // Now position the draggable and set it to the input. - const position = CoreDomUtils.getElementXY(drop, undefined, 'ddarea'); + const ddArea = this.container.querySelector('.ddarea'); + if (!ddArea) { + return; + } + + const position = CoreDomUtils.getRelativeElementPosition(drop, ddArea); const choice = drag.getAttribute('choice'); - drag.style.left = position[0] - 1 + 'px'; - drag.style.top = position[1] - 1 + 'px'; + drag.style.left = position.x + 'px'; + drag.style.top = position.y + 'px'; drag.classList.add('placed'); if (choice) { @@ -458,13 +468,14 @@ export class AddonQtypeDdImageOrTextQuestion { // Move the element to its original position. const dragItemHome = this.doc.dragItemHome(Number(drag.getAttribute('dragitemno'))); - if (!dragItemHome) { + const ddArea = this.container.querySelector('.ddarea'); + if (!dragItemHome || !ddArea) { return; } - const position = CoreDomUtils.getElementXY(dragItemHome, undefined, 'ddarea'); - drag.style.left = position[0] + 'px'; - drag.style.top = position[1] + 'px'; + const position = CoreDomUtils.getRelativeElementPosition(dragItemHome, ddArea); + drag.style.left = position.x + 'px'; + drag.style.top = position.y + 'px'; drag.classList.remove('placed'); drag.setAttribute('inputid', ''); diff --git a/src/addons/qtype/ddmarker/classes/ddmarker.ts b/src/addons/qtype/ddmarker/classes/ddmarker.ts index ab7350a80..89b5a997d 100644 --- a/src/addons/qtype/ddmarker/classes/ddmarker.ts +++ b/src/addons/qtype/ddmarker/classes/ddmarker.ts @@ -12,21 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreCoordinates, CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { CoreEventObserver } from '@singletons/events'; import { CoreLogger } from '@singletons/logger'; import { AddonQtypeDdMarkerQuestionData } from '../component/ddmarker'; import { AddonQtypeDdMarkerGraphicsApi } from './graphics_api'; -/** - * Point type. - */ -export type AddonQtypeDdMarkerQuestionPoint = { - x: number; // X axis coordinates. - y: number; // Y axis coordinates. -}; - /** * Class to make a question of ddmarker type work. */ @@ -36,8 +28,7 @@ export class AddonQtypeDdMarkerQuestion { protected logger: CoreLogger; protected afterImageLoadDone = false; - protected drops; - protected topNode; + protected topNode?: HTMLElement | null; protected nextColourIndex = 0; protected proportion = 1; protected selected?: HTMLElement; // Selected element (being "dragged"). @@ -123,7 +114,7 @@ export class AddonQtypeDdMarkerQuestion { return []; } - const position = CoreDomUtils.getElementXY(bgImg, undefined, 'ddarea'); + const position = this.getElementCoordinates(bgImg); let coordsNumbers = this.parsePoint(bgImgXY); coordsNumbers = this.makePointProportional(coordsNumbers); @@ -131,13 +122,30 @@ export class AddonQtypeDdMarkerQuestion { return [coordsNumbers.x + position[0], coordsNumbers.y + position[1]]; } + /** + * Returns elements coordinates relative to ddarea container. + * + * @param element Element. + * @return Array of X and Y coordinates. + */ + protected getElementCoordinates(element: HTMLElement): number[] { + const ddArea = this.container.querySelector('.ddarea'); + if (!ddArea) { + return []; + } + + const position = CoreDomUtils.getRelativeElementPosition(element, ddArea); + + return [position.x, position.y]; + } + /** * Check if some coordinates (X, Y) are inside the background image. * * @param coords Coordinates to check. * @return Whether they're inside the background image. */ - coordsInImg(coords: AddonQtypeDdMarkerQuestionPoint): boolean { + coordsInImg(coords: CoreCoordinates): boolean { const bgImg = this.doc.bgImg(); if (!bgImg) { return false; @@ -177,13 +185,13 @@ export class AddonQtypeDdMarkerQuestion { const dragging = this.selected; if (dragging && !drag.classList.contains('unplaced')) { - const position = CoreDomUtils.getElementXY(drag, undefined, 'ddarea'); + const position = this.getElementCoordinates(drag); const bgImg = this.doc.bgImg(); if (!bgImg) { return; } - const bgImgPos = CoreDomUtils.getElementXY(bgImg, undefined, 'ddarea'); + const bgImgPos = this.getElementCoordinates(bgImg); position[0] = position[0] - bgImgPos[0] + e.offsetX; position[1] = position[1] - bgImgPos[1] + e.offsetY; @@ -217,7 +225,7 @@ export class AddonQtypeDdMarkerQuestion { return []; } - const position = CoreDomUtils.getElementXY(dragItemHome, undefined, 'ddarea'); + const position = this.getElementCoordinates(dragItemHome); return [position[0], position[1]]; } @@ -317,7 +325,7 @@ export class AddonQtypeDdMarkerQuestion { * @param colour Colour of the circle. * @return X and Y position of the center of the circle. */ - drawShapeCircle(dropZoneNo: number, coordinates: string, colour: string): AddonQtypeDdMarkerQuestionPoint | null { + drawShapeCircle(dropZoneNo: number, coordinates: string, colour: string): CoreCoordinates | null { if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?;\d+(\.\d+)?$/)) { return null; } @@ -356,7 +364,7 @@ export class AddonQtypeDdMarkerQuestion { * @param colour Colour of the rectangle. * @return X and Y position of the center of the rectangle. */ - drawShapeRectangle(dropZoneNo: number, coordinates: string, colour: string): AddonQtypeDdMarkerQuestionPoint | null { + drawShapeRectangle(dropZoneNo: number, coordinates: string, colour: string): CoreCoordinates | null { if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?;\d+(\.\d+)?,\d+(\.\d+)?$/)) { return null; } @@ -399,7 +407,7 @@ export class AddonQtypeDdMarkerQuestion { * @param colour Colour of the polygon. * @return X and Y position of the center of the polygon. */ - drawShapePolygon(dropZoneNo: number, coordinates: string, colour: string): AddonQtypeDdMarkerQuestionPoint | null { + drawShapePolygon(dropZoneNo: number, coordinates: string, colour: string): CoreCoordinates | null { if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?(?:;\d+(\.\d+)?,\d+(\.\d+)?)*$/)) { return null; } @@ -449,7 +457,7 @@ export class AddonQtypeDdMarkerQuestion { * @param coordinates "x,y". * @return Coordinates to the point. */ - parsePoint(coordinates: string): AddonQtypeDdMarkerQuestionPoint { + parsePoint(coordinates: string): CoreCoordinates { const bits = coordinates.split(','); if (bits.length !== 2) { throw coordinates + ' is not a valid point'; @@ -464,7 +472,7 @@ export class AddonQtypeDdMarkerQuestion { * @param point Point coordinates. * @return Converted point. */ - makePointProportional(point: AddonQtypeDdMarkerQuestionPoint): AddonQtypeDdMarkerQuestionPoint { + makePointProportional(point: CoreCoordinates): CoreCoordinates { return { x: Math.round(point.x * this.proportion), y: Math.round(point.y * this.proportion), @@ -542,10 +550,10 @@ export class AddonQtypeDdMarkerQuestion { * @return Coordinates. */ getDragXY(dragItem: HTMLElement): number[] { - const position = CoreDomUtils.getElementXY(dragItem, undefined, 'ddarea'); + const position = this.getElementCoordinates(dragItem); const bgImg = this.doc.bgImg(); if (bgImg) { - const bgImgXY = CoreDomUtils.getElementXY(bgImg, undefined, 'ddarea'); + const bgImgXY = this.getElementCoordinates(bgImg); position[0] -= bgImgXY[0]; position[1] -= bgImgXY[1]; diff --git a/src/addons/qtype/ddmarker/classes/graphics_api.ts b/src/addons/qtype/ddmarker/classes/graphics_api.ts index 8ab982905..1c66def35 100644 --- a/src/addons/qtype/ddmarker/classes/graphics_api.ts +++ b/src/addons/qtype/ddmarker/classes/graphics_api.ts @@ -23,12 +23,6 @@ export class AddonQtypeDdMarkerGraphicsApi { protected readonly NS = 'http://www.w3.org/2000/svg'; protected dropZone?: SVGSVGElement; - /** - * Create the instance. - * - * @param instance Question instance. - * @param domUtils Dom Utils provider. - */ constructor(protected instance: AddonQtypeDdMarkerQuestion) { } /** @@ -60,20 +54,20 @@ export class AddonQtypeDdMarkerGraphicsApi { const bgImg = this.instance.doc?.bgImg(); const dropZones = this.instance.doc?.topNode?.querySelector('div.ddarea div.dropzones'); const markerTexts = this.instance.doc?.markerTexts(); - - if (!bgImg || !dropZones || !markerTexts) { + const ddArea = this.instance.doc?.topNode?.querySelector('.ddarea'); + if (!bgImg || !dropZones || !markerTexts || !ddArea) { return; } - const position = CoreDomUtils.getElementXY(bgImg, undefined, 'ddarea'); + const position = CoreDomUtils.getRelativeElementPosition(bgImg, ddArea); - dropZones.style.left = position[0] + 'px'; - dropZones.style.top = position[1] + 'px'; + dropZones.style.left = position.x + 'px'; + dropZones.style.top = position.y + 'px'; dropZones.style.width = bgImg.width + 'px'; dropZones.style.height = bgImg.height + 'px'; - markerTexts.style.left = position[0] + 'px'; - markerTexts.style.top = position[1] + 'px'; + markerTexts.style.left = position.x + 'px'; + markerTexts.style.top = position.y + 'px'; markerTexts.style.width = bgImg.width + 'px'; markerTexts.style.height = bgImg.height + 'px'; diff --git a/src/addons/qtype/ddwtos/classes/ddwtos.ts b/src/addons/qtype/ddwtos/classes/ddwtos.ts index 29ab292dc..60be1af6d 100644 --- a/src/addons/qtype/ddwtos/classes/ddwtos.ts +++ b/src/addons/qtype/ddwtos/classes/ddwtos.ts @@ -13,7 +13,7 @@ // limitations under the License. import { CoreFormatTextDirective } from '@directives/format-text'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreCoordinates, CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { CoreComponentsRegistry } from '@singletons/components-registry'; @@ -375,33 +375,37 @@ export class AddonQtypeDdwtosQuestion { return; } - let position; - const placeNo = this.placed[this.getNo(drag) ?? -1]; + const parent = this.container.querySelector('.addon-qtype-ddwtos-container'); + if (!parent) { + return; + } + + let position: CoreCoordinates | undefined; + if (!placeNo) { // Not placed, put it in home zone. const groupNo = this.getGroup(drag) ?? -1; const choiceNo = this.getChoice(drag) ?? -1; - - position = CoreDomUtils.getElementXY( - this.container, - this.selectors.dragHome(groupNo, choiceNo), - 'answercontainer', - ); - drag.classList.add('unplaced'); + const dragHome = this.container.querySelector(this.selectors.dragHome(groupNo, choiceNo)); + if (dragHome) { + position = CoreDomUtils.getRelativeElementPosition(dragHome, parent); + } } else { // Get the drop zone position. - position = CoreDomUtils.getElementXY( - this.container, - this.selectors.dropForPlace(placeNo), - 'addon-qtype-ddwtos-container', - ); - drag.classList.remove('unplaced'); + const dropZone = this.container.querySelector(this.selectors.dropForPlace(placeNo)); + if (dropZone) { + position = CoreDomUtils.getRelativeElementPosition(dropZone, parent); + // Avoid the border. + position.x++; + position.y++; + } } + drag.classList.toggle('unplaced', !placeNo); if (position) { - drag.style.left = position[0] + 'px'; - drag.style.top = position[1] + 'px'; + drag.style.left = position.x + 'px'; + drag.style.top = position.y + 'px'; } } diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index d718396ca..797faa9fb 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -720,50 +720,35 @@ export class CoreDomUtilsProvider { /** * Retrieve the position of a element relative to another element. * - * @param container Element to search in. + * @param element Element to search in. * @param selector Selector to find the element to gets the position. * @param positionParentClass Parent Class where to stop calculating the position. Default inner-scroll. * @return positionLeft, positionTop of the element relative to. + * @deprecated since app 4.0. Use getRelativeElementPosition instead. */ - getElementXY(container: HTMLElement, selector?: undefined, positionParentClass?: string): number[]; - getElementXY(container: HTMLElement, selector: string, positionParentClass?: string): number[] | null; - getElementXY(container: HTMLElement, selector?: string, positionParentClass = 'inner-scroll'): number[] | null { - let element = (selector ? container.querySelector(selector) : container); - if (!element) { + getElementXY(element: HTMLElement, selector?: string, positionParentClass = 'inner-scroll'): [number, number] | null { + if (selector) { + const foundElement = element.querySelector(selector); + if (!foundElement) { + // Element not found. + return null; + } + + element = foundElement; + } + + const parent = element.closest(`.${positionParentClass}`); + if (!parent) { return null; } - let positionTop = 0; - let positionLeft = 0; + const position = CoreDomUtils.getRelativeElementPosition(element, parent); - if (!positionParentClass) { - positionParentClass = 'inner-scroll'; - } - - while (element) { - positionLeft += (element.offsetLeft - element.scrollLeft + element.clientLeft); - positionTop += (element.offsetTop - element.scrollTop + element.clientTop); - - const offsetElement = element.offsetParent; - element = element.parentElement; - - // Every parent class has to be checked but the position has to be got form offsetParent. - while (offsetElement != element && element) { - // If positionParentClass element is reached, stop adding tops. - if (element.className.indexOf(positionParentClass) != -1) { - element = null; - } else { - element = element.parentElement; - } - } - - // Finally, check again. - if (element?.className.indexOf(positionParentClass) != -1) { - element = null; - } - } - - return [positionLeft, positionTop]; + // Calculate the top and left positions. + return [ + Math.ceil(position.x), + Math.ceil(position.y), + ]; } /** @@ -773,12 +758,12 @@ export class CoreDomUtilsProvider { * @param parent Parent element to get relative position. * @return X and Y position. */ - getRelativeElementPosition(element: HTMLElement, parent: HTMLElement): { x: number; y: number} { + getRelativeElementPosition(element: HTMLElement, parent: HTMLElement): CoreCoordinates { // Get the top, left coordinates of two elements const elementRectangle = element.getBoundingClientRect(); const parentRectangle = parent.getBoundingClientRect(); - // Calculate the top and left positions + // Calculate the top and left positions. return { x: elementRectangle.x - parentRectangle.x, y: elementRectangle.y - parentRectangle.y, @@ -2442,3 +2427,11 @@ export enum VerticalPoint { MID = 'mid', BOTTOM = 'bottom', } + +/** + * Coordinates of an element. + */ +export type CoreCoordinates = { + x: number; // X axis coordinates. + y: number; // Y axis coordinates. +};