From e654fba8aeff356982aa7d853456c6e5e6cd199c Mon Sep 17 00:00:00 2001 From: mahmoud adel <58145645+mahmoudadel54@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:16:23 +0200 Subject: [PATCH] #9553: Improving readability of long attribute values in attribute table and table widgets (#9701) (#9719) * #9553: handle showing tooltip on attr table cells once user hovers on cell * #9553: create a separate enhnacer for handleLongText and use it for formatter table cell * #9553: add copyright for the created handleLongTextEnhancer * #9553: handle test cases for handleLongTextEnhancer * #9553: add unit tests for handleLongTextEnhancer * #9553: fix unit test failure for featureTypeToGridColumns formatters * #9553:reset tests.webpack file * Update web/client/components/misc/enhancers/handleLongTextEnhancer.jsx --------- Co-authored-by: Matteo V --- .../data/featuregrid/formatters/index.js | 2 +- .../__tests__/handleLongTextEnhancer-test.jsx | 76 +++++++++++++++++++ .../misc/enhancers/handleLongTextEnhancer.jsx | 50 ++++++++++++ web/client/utils/FeatureGridUtils.js | 4 +- .../utils/__tests__/FeatureGridUtils-test.js | 35 +++++++++ 5 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 web/client/components/misc/enhancers/__tests__/handleLongTextEnhancer-test.jsx create mode 100644 web/client/components/misc/enhancers/handleLongTextEnhancer.jsx diff --git a/web/client/components/data/featuregrid/formatters/index.js b/web/client/components/data/featuregrid/formatters/index.js index 28f2d339a6..5f2e4104e4 100644 --- a/web/client/components/data/featuregrid/formatters/index.js +++ b/web/client/components/data/featuregrid/formatters/index.js @@ -15,7 +15,7 @@ import NumberFormat from '../../../I18N/Number'; import { dateFormats as defaultDateFormats } from "../../../../utils/FeatureGridUtils"; const BooleanFormatter = ({value} = {}) => !isNil(value) ? {value.toString()} : null; -const StringFormatter = ({value} = {}) => !isNil(value) ? reactStringReplace(value, /(https?:\/\/\S+)/g, (match, i) => ( +export const StringFormatter = ({value} = {}) => !isNil(value) ? reactStringReplace(value, /(https?:\/\/\S+)/g, (match, i) => ( {match} )) : null; const NumberFormatter = ({value} = {}) => !isNil(value) ? : null; diff --git a/web/client/components/misc/enhancers/__tests__/handleLongTextEnhancer-test.jsx b/web/client/components/misc/enhancers/__tests__/handleLongTextEnhancer-test.jsx new file mode 100644 index 0000000000..24faa3e6ea --- /dev/null +++ b/web/client/components/misc/enhancers/__tests__/handleLongTextEnhancer-test.jsx @@ -0,0 +1,76 @@ +/** + * Copyright 2023, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import expect from 'expect'; + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { handleLongTextEnhancer } from '../handleLongTextEnhancer'; +import { StringFormatter } from '../../../data/featuregrid/formatters'; + +describe("handleLongTextEnhancer enhancer", () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + + it('handleLongTextEnhancer by passing formatter as wrapper', () => { + const EnhancerWithFormatter = ()=> handleLongTextEnhancer(StringFormatter)({ value: "test12334567899999" }); + ReactDOM.render( + , + document.getElementById("container") + ); + expect(document.getElementById("container").innerHTML).toExist(); + expect(document.getElementsByTagName('span').length).toEqual(2); + expect(document.getElementsByTagName('span')[1].innerHTML).toExist(); + }); + + it('handleLongTextEnhancer with by passing td as wrapper', () => { + const wrapper = () => (15234568965); + const EnhancerWithFormatter = ()=> handleLongTextEnhancer(wrapper)({ value: "15234568965" }); + ReactDOM.render( + , + document.getElementById("container") + ); + expect(document.getElementById("container").innerHTML).toExist(); + expect(document.getElementsByTagName('span').length).toEqual(2); + expect(document.getElementsByTagName('span')[1].innerHTML).toExist(); + }); + + + it('handleLongTextEnhancer with by passing span as wrapper', () => { + const wrapper = () => (15234568965); + const EnhancerWithFormatter = ()=> handleLongTextEnhancer(wrapper)({ value: "15234568965" }); + ReactDOM.render( + , + document.getElementById("container") + ); + expect(document.getElementById("container").innerHTML).toExist(); + expect(document.getElementsByTagName('span').length).toEqual(3); + expect(document.getElementsByTagName('span')[1].innerHTML).toExist(); + }); + + + it('handleLongTextEnhancer with by passing td div wrapper', () => { + const wrapper = () => (
test
); + const EnhancerWithFormatter = ()=> handleLongTextEnhancer(wrapper)({ value: "test" }); + ReactDOM.render( + , + document.getElementById("container") + ); + expect(document.getElementById("container").innerHTML).toExist(); + expect(document.getElementsByTagName('span').length).toEqual(2); + expect(document.getElementsByTagName('span')[1].innerHTML).toExist(); + }); +}); diff --git a/web/client/components/misc/enhancers/handleLongTextEnhancer.jsx b/web/client/components/misc/enhancers/handleLongTextEnhancer.jsx new file mode 100644 index 0000000000..7b75d8a321 --- /dev/null +++ b/web/client/components/misc/enhancers/handleLongTextEnhancer.jsx @@ -0,0 +1,50 @@ +/* +* Copyright 2023, GeoSolutions Sas. +* All rights reserved. +* +* This source code is licensed under the BSD-style license found in the +* LICENSE file in the root directory of this source tree. +*/ + +import React from "react"; +import OverlayTrigger from "../OverlayTrigger"; +import { Tooltip } from "react-bootstrap"; +/** + * handleLongTextEnhancer enhancer. Enhances a long text content by adding a tooltip. + * @type {function} + * @name handleLongTextEnhancer + * @memberof components.misc.enhancers + * Wraps [wrapped component with content] to add tooltip for long content if shown content less than the main content + * @param {Component} Wrapped the component wrapped with a tooltip when its content is too long + * @param {object} props the props that contains value content + * @return {Component} the rendered component that renders the content with the tooltip if the content is long or renders the content with no tooltip if not long + * @example + * const wrapper = () = > testtttttttttt + * const Component = ()=> handleLongTextEnhancer(wrapper)(props); + * render (){ + * return + * } + * + */ +export const handleLongTextEnhancer = (Wrapped) => (props) => { + const cellRef = React.useRef(null); + const contentRef = React.useRef(null); + const [isContentOverflowing, setIsContentOverflowing] = React.useState(false); + + const handleMouseEnter = () => { + const cellWidth = cellRef.current.offsetWidth; + const contentWidth = contentRef.current.offsetWidth; + setIsContentOverflowing(contentWidth > cellWidth); + }; + + return ({} : <>} + > +
+ + {} + +
+
); +}; diff --git a/web/client/utils/FeatureGridUtils.js b/web/client/utils/FeatureGridUtils.js index 19e41a2643..190f62cb91 100644 --- a/web/client/utils/FeatureGridUtils.js +++ b/web/client/utils/FeatureGridUtils.js @@ -18,6 +18,7 @@ import { } from './ogc/WFS/base'; import { applyDefaultToLocalizedString } from '../components/I18N/LocalizedString'; +import { handleLongTextEnhancer } from '../components/misc/enhancers/handleLongTextEnhancer'; const getGeometryName = (describe) => get(findGeometryProperty(describe), "name"); const getPropertyName = (name, describe) => name === "geometry" ? getGeometryName(describe) : name; @@ -115,6 +116,7 @@ export const getCurrentPaginationOptions = ({ startPage, endPage }, oldPages, si return { startIndex: nPs[0] * size, maxFeatures: needPages * size }; }; + /** * Utility function to get from a describeFeatureType response the columns to use in the react-data-grid * @param {object} describe describeFeatureType response @@ -146,7 +148,7 @@ export const featureTypeToGridColumns = ( editable, filterable, editor: getEditor(desc, field), - formatter: getFormatter(desc, field), + formatter: handleLongTextEnhancer(getFormatter(desc, field)), filterRenderer: getFilterRenderer(desc, field) }; }); diff --git a/web/client/utils/__tests__/FeatureGridUtils-test.js b/web/client/utils/__tests__/FeatureGridUtils-test.js index 68d081aca3..aafbbac3ea 100644 --- a/web/client/utils/__tests__/FeatureGridUtils-test.js +++ b/web/client/utils/__tests__/FeatureGridUtils-test.js @@ -5,7 +5,10 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ +import React from "react"; +import ReactDOM from "react-dom"; import expect from 'expect'; + import { updatePages, gridUpdateToQueryUpdate, @@ -18,6 +21,18 @@ import { describe('FeatureGridUtils', () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + + afterEach((done) => { + ReactDOM.unmountComponentAtNode( + document.getElementById("container") + ); + document.body.innerHTML = ""; + setTimeout(done); + }); it('Test updatePages when needPages * size is less then features', () => { const oldFeatures = Array(350); const features = Array(60); @@ -332,6 +347,26 @@ describe('FeatureGridUtils', () => { // test localized alias with empty default expect(featureTypeToGridColumns(describe, columnSettings, [{name: "Test1", alias: {"default": ""}}])[0].title.default).toEqual('Test1'); + }); + it('featureTypeToGridColumns formatters', () => { + const DUMMY = () => {}; + const formatterWrapper = () => (
testtttt
); + const describe = {featureTypes: [{properties: [{name: 'Test1', type: "xsd:number"}, {name: 'Test2', type: "xsd:number"}]}]}; + const columnSettings = {name: 'Test1', hide: false}; + const options = [{name: 'Test1', title: 'Some title', description: 'Some description'}]; + const featureGridColumns = featureTypeToGridColumns(describe, columnSettings, [], {options}, {getHeaderRenderer: () => DUMMY, getFilterRenderer: () => DUMMY, getFormatter: () => formatterWrapper, getEditor: () => DUMMY}); + expect(featureGridColumns.length).toBe(2); + featureGridColumns.forEach((fgColumns)=>{ + const Formatter = fgColumns.formatter; + ReactDOM.render( + , + document.getElementById("container") + ); + expect(document.getElementById("container").innerHTML).toExist(); + expect(document.getElementsByTagName('span').length).toEqual(2); + expect(document.getElementsByTagName('span')[1].innerHTML).toExist(); + }); + }); describe("supportsFeatureEditing", () => { it('test supportsFeatureEditing with valid layer type', () => {