MOBILE-3814 dom: Improve element position

main
Pau Ferrer Ocaña 2022-03-21 15:58:44 +01:00
parent a76914f25a
commit d500d1fd08
5 changed files with 111 additions and 101 deletions

View File

@ -78,13 +78,18 @@ export class AddonQtypeDdImageOrTextQuestion {
return bgImgXY; return bgImgXY;
} }
const position = CoreDomUtils.getElementXY(bgImg, undefined, 'ddarea'); const ddArea = this.container.querySelector<HTMLElement>('.ddarea');
if (!ddArea) {
return bgImgXY;
}
const position = CoreDomUtils.getRelativeElementPosition(bgImg, ddArea);
// Render the position related to the current image dimensions. // Render the position related to the current image dimensions.
bgImgXY[0] *= this.proportion; bgImgXY[0] *= this.proportion;
bgImgXY[1] *= 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. // Now position the draggable and set it to the input.
const position = CoreDomUtils.getElementXY(drop, undefined, 'ddarea'); const ddArea = this.container.querySelector<HTMLElement>('.ddarea');
if (!ddArea) {
return;
}
const position = CoreDomUtils.getRelativeElementPosition(drop, ddArea);
const choice = drag.getAttribute('choice'); const choice = drag.getAttribute('choice');
drag.style.left = position[0] - 1 + 'px'; drag.style.left = position.x + 'px';
drag.style.top = position[1] - 1 + 'px'; drag.style.top = position.y + 'px';
drag.classList.add('placed'); drag.classList.add('placed');
if (choice) { if (choice) {
@ -458,13 +468,14 @@ export class AddonQtypeDdImageOrTextQuestion {
// Move the element to its original position. // Move the element to its original position.
const dragItemHome = this.doc.dragItemHome(Number(drag.getAttribute('dragitemno'))); const dragItemHome = this.doc.dragItemHome(Number(drag.getAttribute('dragitemno')));
if (!dragItemHome) { const ddArea = this.container.querySelector<HTMLElement>('.ddarea');
if (!dragItemHome || !ddArea) {
return; return;
} }
const position = CoreDomUtils.getElementXY(dragItemHome, undefined, 'ddarea'); const position = CoreDomUtils.getRelativeElementPosition(dragItemHome, ddArea);
drag.style.left = position[0] + 'px'; drag.style.left = position.x + 'px';
drag.style.top = position[1] + 'px'; drag.style.top = position.y + 'px';
drag.classList.remove('placed'); drag.classList.remove('placed');
drag.setAttribute('inputid', ''); drag.setAttribute('inputid', '');

View File

@ -12,21 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { CoreDomUtils } from '@services/utils/dom'; import { CoreCoordinates, CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreEventObserver } from '@singletons/events'; import { CoreEventObserver } from '@singletons/events';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { AddonQtypeDdMarkerQuestionData } from '../component/ddmarker'; import { AddonQtypeDdMarkerQuestionData } from '../component/ddmarker';
import { AddonQtypeDdMarkerGraphicsApi } from './graphics_api'; 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. * Class to make a question of ddmarker type work.
*/ */
@ -36,8 +28,7 @@ export class AddonQtypeDdMarkerQuestion {
protected logger: CoreLogger; protected logger: CoreLogger;
protected afterImageLoadDone = false; protected afterImageLoadDone = false;
protected drops; protected topNode?: HTMLElement | null;
protected topNode;
protected nextColourIndex = 0; protected nextColourIndex = 0;
protected proportion = 1; protected proportion = 1;
protected selected?: HTMLElement; // Selected element (being "dragged"). protected selected?: HTMLElement; // Selected element (being "dragged").
@ -123,7 +114,7 @@ export class AddonQtypeDdMarkerQuestion {
return []; return [];
} }
const position = CoreDomUtils.getElementXY(bgImg, undefined, 'ddarea'); const position = this.getElementCoordinates(bgImg);
let coordsNumbers = this.parsePoint(bgImgXY); let coordsNumbers = this.parsePoint(bgImgXY);
coordsNumbers = this.makePointProportional(coordsNumbers); coordsNumbers = this.makePointProportional(coordsNumbers);
@ -131,13 +122,30 @@ export class AddonQtypeDdMarkerQuestion {
return [coordsNumbers.x + position[0], coordsNumbers.y + position[1]]; 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<HTMLElement>('.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. * Check if some coordinates (X, Y) are inside the background image.
* *
* @param coords Coordinates to check. * @param coords Coordinates to check.
* @return Whether they're inside the background image. * @return Whether they're inside the background image.
*/ */
coordsInImg(coords: AddonQtypeDdMarkerQuestionPoint): boolean { coordsInImg(coords: CoreCoordinates): boolean {
const bgImg = this.doc.bgImg(); const bgImg = this.doc.bgImg();
if (!bgImg) { if (!bgImg) {
return false; return false;
@ -177,13 +185,13 @@ export class AddonQtypeDdMarkerQuestion {
const dragging = this.selected; const dragging = this.selected;
if (dragging && !drag.classList.contains('unplaced')) { if (dragging && !drag.classList.contains('unplaced')) {
const position = CoreDomUtils.getElementXY(drag, undefined, 'ddarea'); const position = this.getElementCoordinates(drag);
const bgImg = this.doc.bgImg(); const bgImg = this.doc.bgImg();
if (!bgImg) { if (!bgImg) {
return; return;
} }
const bgImgPos = CoreDomUtils.getElementXY(bgImg, undefined, 'ddarea'); const bgImgPos = this.getElementCoordinates(bgImg);
position[0] = position[0] - bgImgPos[0] + e.offsetX; position[0] = position[0] - bgImgPos[0] + e.offsetX;
position[1] = position[1] - bgImgPos[1] + e.offsetY; position[1] = position[1] - bgImgPos[1] + e.offsetY;
@ -217,7 +225,7 @@ export class AddonQtypeDdMarkerQuestion {
return []; return [];
} }
const position = CoreDomUtils.getElementXY(dragItemHome, undefined, 'ddarea'); const position = this.getElementCoordinates(dragItemHome);
return [position[0], position[1]]; return [position[0], position[1]];
} }
@ -317,7 +325,7 @@ export class AddonQtypeDdMarkerQuestion {
* @param colour Colour of the circle. * @param colour Colour of the circle.
* @return X and Y position of the center 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+)?$/)) { if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?;\d+(\.\d+)?$/)) {
return null; return null;
} }
@ -356,7 +364,7 @@ export class AddonQtypeDdMarkerQuestion {
* @param colour Colour of the rectangle. * @param colour Colour of the rectangle.
* @return X and Y position of the center 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+)?$/)) { if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?;\d+(\.\d+)?,\d+(\.\d+)?$/)) {
return null; return null;
} }
@ -399,7 +407,7 @@ export class AddonQtypeDdMarkerQuestion {
* @param colour Colour of the polygon. * @param colour Colour of the polygon.
* @return X and Y position of the center 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+)?)*$/)) { if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?(?:;\d+(\.\d+)?,\d+(\.\d+)?)*$/)) {
return null; return null;
} }
@ -449,7 +457,7 @@ export class AddonQtypeDdMarkerQuestion {
* @param coordinates "x,y". * @param coordinates "x,y".
* @return Coordinates to the point. * @return Coordinates to the point.
*/ */
parsePoint(coordinates: string): AddonQtypeDdMarkerQuestionPoint { parsePoint(coordinates: string): CoreCoordinates {
const bits = coordinates.split(','); const bits = coordinates.split(',');
if (bits.length !== 2) { if (bits.length !== 2) {
throw coordinates + ' is not a valid point'; throw coordinates + ' is not a valid point';
@ -464,7 +472,7 @@ export class AddonQtypeDdMarkerQuestion {
* @param point Point coordinates. * @param point Point coordinates.
* @return Converted point. * @return Converted point.
*/ */
makePointProportional(point: AddonQtypeDdMarkerQuestionPoint): AddonQtypeDdMarkerQuestionPoint { makePointProportional(point: CoreCoordinates): CoreCoordinates {
return { return {
x: Math.round(point.x * this.proportion), x: Math.round(point.x * this.proportion),
y: Math.round(point.y * this.proportion), y: Math.round(point.y * this.proportion),
@ -542,10 +550,10 @@ export class AddonQtypeDdMarkerQuestion {
* @return Coordinates. * @return Coordinates.
*/ */
getDragXY(dragItem: HTMLElement): number[] { getDragXY(dragItem: HTMLElement): number[] {
const position = CoreDomUtils.getElementXY(dragItem, undefined, 'ddarea'); const position = this.getElementCoordinates(dragItem);
const bgImg = this.doc.bgImg(); const bgImg = this.doc.bgImg();
if (bgImg) { if (bgImg) {
const bgImgXY = CoreDomUtils.getElementXY(bgImg, undefined, 'ddarea'); const bgImgXY = this.getElementCoordinates(bgImg);
position[0] -= bgImgXY[0]; position[0] -= bgImgXY[0];
position[1] -= bgImgXY[1]; position[1] -= bgImgXY[1];

View File

@ -23,12 +23,6 @@ export class AddonQtypeDdMarkerGraphicsApi {
protected readonly NS = 'http://www.w3.org/2000/svg'; protected readonly NS = 'http://www.w3.org/2000/svg';
protected dropZone?: SVGSVGElement; protected dropZone?: SVGSVGElement;
/**
* Create the instance.
*
* @param instance Question instance.
* @param domUtils Dom Utils provider.
*/
constructor(protected instance: AddonQtypeDdMarkerQuestion) { } constructor(protected instance: AddonQtypeDdMarkerQuestion) { }
/** /**
@ -60,20 +54,20 @@ export class AddonQtypeDdMarkerGraphicsApi {
const bgImg = this.instance.doc?.bgImg(); const bgImg = this.instance.doc?.bgImg();
const dropZones = this.instance.doc?.topNode?.querySelector<HTMLElement>('div.ddarea div.dropzones'); const dropZones = this.instance.doc?.topNode?.querySelector<HTMLElement>('div.ddarea div.dropzones');
const markerTexts = this.instance.doc?.markerTexts(); const markerTexts = this.instance.doc?.markerTexts();
const ddArea = this.instance.doc?.topNode?.querySelector<HTMLElement>('.ddarea');
if (!bgImg || !dropZones || !markerTexts) { if (!bgImg || !dropZones || !markerTexts || !ddArea) {
return; return;
} }
const position = CoreDomUtils.getElementXY(bgImg, undefined, 'ddarea'); const position = CoreDomUtils.getRelativeElementPosition(bgImg, ddArea);
dropZones.style.left = position[0] + 'px'; dropZones.style.left = position.x + 'px';
dropZones.style.top = position[1] + 'px'; dropZones.style.top = position.y + 'px';
dropZones.style.width = bgImg.width + 'px'; dropZones.style.width = bgImg.width + 'px';
dropZones.style.height = bgImg.height + 'px'; dropZones.style.height = bgImg.height + 'px';
markerTexts.style.left = position[0] + 'px'; markerTexts.style.left = position.x + 'px';
markerTexts.style.top = position[1] + 'px'; markerTexts.style.top = position.y + 'px';
markerTexts.style.width = bgImg.width + 'px'; markerTexts.style.width = bgImg.width + 'px';
markerTexts.style.height = bgImg.height + 'px'; markerTexts.style.height = bgImg.height + 'px';

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import { CoreFormatTextDirective } from '@directives/format-text'; 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 { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreComponentsRegistry } from '@singletons/components-registry';
@ -375,33 +375,37 @@ export class AddonQtypeDdwtosQuestion {
return; return;
} }
let position;
const placeNo = this.placed[this.getNo(drag) ?? -1]; const placeNo = this.placed[this.getNo(drag) ?? -1];
const parent = this.container.querySelector<HTMLElement>('.addon-qtype-ddwtos-container');
if (!parent) {
return;
}
let position: CoreCoordinates | undefined;
if (!placeNo) { if (!placeNo) {
// Not placed, put it in home zone. // Not placed, put it in home zone.
const groupNo = this.getGroup(drag) ?? -1; const groupNo = this.getGroup(drag) ?? -1;
const choiceNo = this.getChoice(drag) ?? -1; const choiceNo = this.getChoice(drag) ?? -1;
const dragHome = this.container.querySelector<HTMLElement>(this.selectors.dragHome(groupNo, choiceNo));
position = CoreDomUtils.getElementXY( if (dragHome) {
this.container, position = CoreDomUtils.getRelativeElementPosition(dragHome, parent);
this.selectors.dragHome(groupNo, choiceNo), }
'answercontainer',
);
drag.classList.add('unplaced');
} else { } else {
// Get the drop zone position. // Get the drop zone position.
position = CoreDomUtils.getElementXY( const dropZone = this.container.querySelector<HTMLElement>(this.selectors.dropForPlace(placeNo));
this.container, if (dropZone) {
this.selectors.dropForPlace(placeNo), position = CoreDomUtils.getRelativeElementPosition(dropZone, parent);
'addon-qtype-ddwtos-container', // Avoid the border.
); position.x++;
drag.classList.remove('unplaced'); position.y++;
} }
}
drag.classList.toggle('unplaced', !placeNo);
if (position) { if (position) {
drag.style.left = position[0] + 'px'; drag.style.left = position.x + 'px';
drag.style.top = position[1] + 'px'; drag.style.top = position.y + 'px';
} }
} }

View File

@ -720,50 +720,35 @@ export class CoreDomUtilsProvider {
/** /**
* Retrieve the position of a element relative to another element. * 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 selector Selector to find the element to gets the position.
* @param positionParentClass Parent Class where to stop calculating the position. Default inner-scroll. * @param positionParentClass Parent Class where to stop calculating the position. Default inner-scroll.
* @return positionLeft, positionTop of the element relative to. * @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(element: HTMLElement, selector?: string, positionParentClass = 'inner-scroll'): [number, number] | null {
getElementXY(container: HTMLElement, selector: string, positionParentClass?: string): number[] | null; if (selector) {
getElementXY(container: HTMLElement, selector?: string, positionParentClass = 'inner-scroll'): number[] | null { const foundElement = element.querySelector<HTMLElement>(selector);
let element = (selector ? container.querySelector<HTMLElement>(selector) : container); if (!foundElement) {
if (!element) { // Element not found.
return null; return null;
} }
let positionTop = 0; element = foundElement;
let positionLeft = 0;
if (!positionParentClass) {
positionParentClass = 'inner-scroll';
} }
while (element) { const parent = element.closest<HTMLElement>(`.${positionParentClass}`);
positionLeft += (element.offsetLeft - element.scrollLeft + element.clientLeft); if (!parent) {
positionTop += (element.offsetTop - element.scrollTop + element.clientTop); return null;
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. const position = CoreDomUtils.getRelativeElementPosition(element, parent);
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. * @param parent Parent element to get relative position.
* @return X and Y 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 // Get the top, left coordinates of two elements
const elementRectangle = element.getBoundingClientRect(); const elementRectangle = element.getBoundingClientRect();
const parentRectangle = parent.getBoundingClientRect(); const parentRectangle = parent.getBoundingClientRect();
// Calculate the top and left positions // Calculate the top and left positions.
return { return {
x: elementRectangle.x - parentRectangle.x, x: elementRectangle.x - parentRectangle.x,
y: elementRectangle.y - parentRectangle.y, y: elementRectangle.y - parentRectangle.y,
@ -2442,3 +2427,11 @@ export enum VerticalPoint {
MID = 'mid', MID = 'mid',
BOTTOM = 'bottom', BOTTOM = 'bottom',
} }
/**
* Coordinates of an element.
*/
export type CoreCoordinates = {
x: number; // X axis coordinates.
y: number; // Y axis coordinates.
};