Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#9553: Improving readability of long attribute values in attribute table and table widgets #9701

Merged
merged 8 commits into from
Nov 21, 2023
2 changes: 1 addition & 1 deletion web/client/components/data/featuregrid/formatters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import NumberFormat from '../../../I18N/Number';
import { dateFormats as defaultDateFormats } from "../../../../utils/FeatureGridUtils";

const BooleanFormatter = ({value} = {}) => !isNil(value) ? <span>{value.toString()}</span> : 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) => (
<a key={match + i} href={match} target={"_blank"}>{match}</a>
)) : null;
const NumberFormatter = ({value} = {}) => !isNil(value) ? <NumberFormat value={value} numberParams={{maximumFractionDigits: 17}}/> : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* 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 = '<div id="container"></div>';
setTimeout(done);
});

afterEach((done) => {
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
document.body.innerHTML = '';
setTimeout(done);
});

it('handleLongTextEnhancer with defaults [with no formatter]', () => {
const Enhancer = handleLongTextEnhancer();
ReactDOM.render(
<Enhancer value={"test12334567899999"} />,
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 formatter', () => {
const EnhancerWithFormatter = handleLongTextEnhancer(StringFormatter);
ReactDOM.render(
<EnhancerWithFormatter value={"test12334567899999"} />,
document.getElementById("container")
);
expect(document.getElementById("container").innerHTML).toExist();
expect(document.getElementsByTagName('span').length).toEqual(2);
expect(document.getElementsByTagName('span')[1].innerHTML).toExist();
});
});
35 changes: 35 additions & 0 deletions web/client/components/misc/enhancers/handleLongTextEnhancer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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";

export const handleLongTextEnhancer = (RenderFormatter) => (props) => {
mahmoudadel54 marked this conversation as resolved.
Show resolved Hide resolved
mahmoudadel54 marked this conversation as resolved.
Show resolved Hide resolved
const { value } = 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 (<OverlayTrigger
placement="top"
overlay={isContentOverflowing ? <Tooltip id="tooltip">{RenderFormatter ? <RenderFormatter {...props} /> : value}</Tooltip> : <></>}
mahmoudadel54 marked this conversation as resolved.
Show resolved Hide resolved
>
<div ref={cellRef} onMouseEnter={handleMouseEnter}>
<span ref={contentRef}>
<span>{RenderFormatter ? <RenderFormatter {...props} /> : value}</span>
</span>
</div>
</OverlayTrigger>);
};
4 changes: 3 additions & 1 deletion web/client/utils/FeatureGridUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
};
});
Expand Down
41 changes: 41 additions & 0 deletions web/client/utils/__tests__/FeatureGridUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -18,6 +21,18 @@ import {


describe('FeatureGridUtils', () => {
beforeEach((done) => {
document.body.innerHTML = '<div id="container"></div>';
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);
Expand Down Expand Up @@ -332,6 +347,32 @@ 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 describe = {featureTypes: [{properties: [{name: 'Test1', type: "xsd:number"}, {name: 'Test2', type: "xsd:number"}, {name: 'Test3', type: "xsd:string"}]}]};
const columnSettings = {name: 'Test1', hide: false};
const fields = [{name: 'Test1', type: "xsd:number", alias: 'Test1 alias'}];
const featureGridColumns = featureTypeToGridColumns(describe, columnSettings, fields);
expect(featureGridColumns.length).toBe(3);
expect(featureGridColumns[0].title).toBe('Test1 alias');
// test alias empty string
expect(featureTypeToGridColumns(describe, columnSettings, [{name: "Test1", alias: ""}])[0].title).toEqual('Test1');
// test localized alias
expect(featureTypeToGridColumns(describe, columnSettings, [{name: "Test1", alias: {"default": "XX"}}])[0].title.default).toEqual('XX');
// test localized alias with empty default
expect(featureTypeToGridColumns(describe, columnSettings, [{name: "Test1", alias: {"default": ""}}])[0].title.default).toEqual('Test1');
const values = [123456, 12.3256, "test"];
featureGridColumns.forEach((fgColumns, index)=>{
const Formatter = fgColumns.formatter;
ReactDOM.render(
<Formatter value={values[index]} />,
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', () => {
Expand Down
Loading