diff --git a/samples/grids/grid/state-persistence-about/src/index.tsx b/samples/grids/grid/state-persistence-about/src/index.tsx index 0930d5924..cd475c32f 100644 --- a/samples/grids/grid/state-persistence-about/src/index.tsx +++ b/samples/grids/grid/state-persistence-about/src/index.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom/client'; -import { ButtonVariant, IgrButton } from 'igniteui-react'; +import { IgrButton } from 'igniteui-react'; import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; import './index.css'; @@ -17,8 +17,8 @@ export default function App() {
By default navigating to the previous page, components will reinitialize as per their initial configuration, therefore the IgrGrid will lose its state.
- What our App Component does is reading the state from the window.localStorage object and applying the corresponding state in the `useEffect`` hook.
- Go Back + What our App Component does is reading the state from the window.localStorage object and applying the corresponding state in the `useEffect` hook.
+ Go Back diff --git a/samples/grids/grid/state-persistence-main/src/index.tsx b/samples/grids/grid/state-persistence-main/src/index.tsx index d5ea19cc9..2a087ca72 100644 --- a/samples/grids/grid/state-persistence-main/src/index.tsx +++ b/samples/grids/grid/state-persistence-main/src/index.tsx @@ -2,11 +2,9 @@ import React, { useEffect, useRef, useState } from 'react'; import ReactDOM from 'react-dom/client'; import { - FilterMode, IgrActionStrip, IgrGrid, IgrColumn, - IgrGridModule, IgrGridPinningActions, IgrGridToolbar, IgrGridToolbarActions, @@ -14,10 +12,9 @@ import { IgrGridToolbarPinning, IgrPaginator, IgrGridState, - IgrGridStateOptions, - GridSelectionMode + IgrGridStateOptions } from 'igniteui-react-grids'; -import { IgrButton, IgrCheckbox, IgrCheckboxModule, IgrCheckboxChangeEventArgs, IgrIcon, IgrIconModule } from 'igniteui-react'; +import { IgrButton, IgrCheckbox, IgrCheckboxChangeEventArgs, IgrIcon } from 'igniteui-react'; import { registerIconFromText } from 'igniteui-webcomponents'; import { CustomersData } from './CustomersData'; @@ -25,12 +22,6 @@ import 'igniteui-react-grids/grids/combined'; import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; import './index.css'; -const mods: any[] = [ - IgrGridModule, - IgrIconModule, - IgrCheckboxModule -]; -mods.forEach((m) => m.register()); const restoreIcon = ''; const saveIcon = ''; @@ -41,6 +32,8 @@ const refreshIcon = '({ cellSelection: true, @@ -55,14 +48,15 @@ export default function App() { rowPinning: true, columnSelection: true }); + const [page, setPage] = useState(0); + const [perPage, setPerPage] = useState(15); + const [totalRecords, setTotalRecords] = useState(gridData.length); let grid: IgrGrid; - function gridRef(ref: IgrGrid) { + const gridRef = (ref: IgrGrid) => { grid = ref; - } - let paginatorRef = useRef(null); - const stateKey = "grid-state"; - let gridStateRef = useRef(null); + }; + const gridStateRef = useRef(null); useEffect(() => { registerIconFromText("restore", restoreIcon, "material"); @@ -79,23 +73,24 @@ export default function App() { }; }, []); - function saveGridState() { + const saveGridState = () => { const state = gridStateRef.current.getStateAsString([]); window.localStorage.setItem(stateKey, state); } - function restoreGridState() { + const restoreGridState = () => { const state = window.localStorage.getItem(stateKey); if (state) { gridStateRef.current.applyStateFromString(state, []); } } - function resetGridState() { - paginatorRef.current.page = 0; - paginatorRef.current.perPage = 15; - paginatorRef.current.totalRecords = gridData.length; - grid.clearFilter(null); + const resetGridState = () => { + setPage(0); + setPerPage(15); + setTotalRecords(gridData.length); + + grid.clearFilter(); grid.sortingExpressions = []; grid.groupingExpressions = []; grid.deselectAllColumns(); @@ -103,41 +98,44 @@ export default function App() { grid.clearCellSelection(); } - function onChange(e: IgrCheckboxChangeEventArgs) { + const onChange = (e: IgrCheckboxChangeEventArgs) => { const s = e.target as IgrCheckbox; + if (s.name === 'allFeatures') { - const bEnabled = e.detail.checked; + const isChecked = e.detail.checked; + + setAllOptions(isChecked); + setOption({ - cellSelection: bEnabled, - rowSelection: bEnabled, - filtering: bEnabled, - advancedFiltering: bEnabled, - paging: bEnabled, - sorting: bEnabled, - groupBy: bEnabled, - columns: bEnabled, - expansion: bEnabled, - rowPinning: bEnabled, - columnSelection: bEnabled + cellSelection: isChecked, + rowSelection: isChecked, + filtering: isChecked, + advancedFiltering: isChecked, + paging: isChecked, + sorting: isChecked, + groupBy: isChecked, + columns: isChecked, + expansion: isChecked, + rowPinning: isChecked, + columnSelection: isChecked }); - for (const key of Object.keys(options)) { - gridStateRef.current.options[key] = bEnabled; - } } else { - gridStateRef.current.options[s.name] = e.detail.checked; + const newOptions = { ...options }; + newOptions[s.name as keyof typeof newOptions] = e.detail.checked; + setOption(newOptions); } } - function leavePage() { + const leavePage = () => { saveGridState(); window.location.replace("./grids/grid/state-persistence-about"); } - function clearStorage() { + const clearStorage = () => { window.localStorage.removeItem(stateKey); } - function reloadPage() { + const reloadPage = () => { window.location.reload(); } @@ -191,7 +189,7 @@ export default function App() { Group By + allowAdvancedFiltering={true} filterMode="excelStyleFilter" columnSelection="multiple" rowSelection="multiple"> @@ -202,7 +200,13 @@ export default function App() { - + setPage(ev.detail)} + onPerPageChange={(ev) => setPerPage(ev.detail)}> + diff --git a/samples/grids/hierarchical-grid/state-persistence-about/src/index.tsx b/samples/grids/hierarchical-grid/state-persistence-about/src/index.tsx index 608a0d22f..a2d862cd3 100644 --- a/samples/grids/hierarchical-grid/state-persistence-about/src/index.tsx +++ b/samples/grids/hierarchical-grid/state-persistence-about/src/index.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom/client'; -import { ButtonVariant, IgrButton } from 'igniteui-react'; +import { IgrButton } from 'igniteui-react'; import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; import './index.css'; @@ -17,8 +17,8 @@ export default function App() {
By default navigating to the previous page, components will reinitialize as per their initial configuration, therefore the IgrHierarchicalGrid will lose its state.
- What our App Component does is reading the state from the window.localStorage object and applying the corresponding state in the `useEffect`` hook.
- Go Back + What our App Component does is reading the state from the window.localStorage object and applying the corresponding state in the `useEffect` hook.
+ Go Back diff --git a/samples/grids/hierarchical-grid/state-persistence-main/src/index.tsx b/samples/grids/hierarchical-grid/state-persistence-main/src/index.tsx index ab0f17b79..11a85da2d 100644 --- a/samples/grids/hierarchical-grid/state-persistence-main/src/index.tsx +++ b/samples/grids/hierarchical-grid/state-persistence-main/src/index.tsx @@ -2,10 +2,8 @@ import React, { useEffect, useRef, useState } from "react"; import ReactDOM from "react-dom/client"; import { - FilterMode, IgrActionStrip, IgrColumn, - IgrGridModule, IgrGridPinningActions, IgrGridToolbar, IgrGridToolbarActions, @@ -14,17 +12,14 @@ import { IgrPaginator, IgrGridState, IgrGridStateOptions, - GridSelectionMode, IgrHierarchicalGrid, IgrRowIsland, } from "igniteui-react-grids"; import { IgrButton, IgrCheckbox, - IgrCheckboxModule, IgrCheckboxChangeEventArgs, IgrIcon, - IgrIconModule, } from "igniteui-react"; import { registerIconFromText } from "igniteui-webcomponents"; import SingersData from "./SingersData.json"; @@ -33,9 +28,6 @@ import "igniteui-react-grids/grids/combined"; import "igniteui-react-grids/grids/themes/light/bootstrap.css"; import "./index.css"; -const mods: any[] = [IgrGridModule, IgrIconModule, IgrCheckboxModule]; -mods.forEach((m) => m.register()); - const restoreIcon = ''; const saveIcon = @@ -51,6 +43,8 @@ const refreshIcon = export default function App() { const gridData = SingersData; + const stateKey = "hierarchical-grid-state"; + const [allOptions, setAllOptions] = useState(true); const [options, setOption] = useState({ cellSelection: true, @@ -65,14 +59,15 @@ export default function App() { columnSelection: true, rowIslands: true, }); + const [page, setPage] = useState(0); + const [perPage, setPerPage] = useState(15); + const [totalRecords, setTotalRecords] = useState(gridData.length); let grid: IgrHierarchicalGrid; - function gridRef(ref: IgrHierarchicalGrid) { + const gridRef = (ref: IgrHierarchicalGrid) => { grid = ref; } - let paginatorRef = useRef(null); - const stateKey = "hierarchical-grid-state"; - let gridStateRef = useRef(null); + const gridStateRef = useRef(null); useEffect(() => { registerIconFromText("restore", restoreIcon, "material"); @@ -89,65 +84,69 @@ export default function App() { }; }, []); - function saveGridState() { + const saveGridState = () => { const state = gridStateRef.current.getStateAsString([]); window.localStorage.setItem(stateKey, state); } - function restoreGridState() { + const restoreGridState = () => { const state = window.localStorage.getItem(stateKey); if (state) { gridStateRef.current.applyStateFromString(state, []); } } - function resetGridState() { - paginatorRef.current.page = 0; - paginatorRef.current.perPage = 15; - paginatorRef.current.totalRecords = gridData.length; - grid.clearFilter(null); + const resetGridState = () => { + setPage(0); + setPerPage(15); + setTotalRecords(gridData.length); + + grid.clearFilter(); grid.sortingExpressions = []; grid.deselectAllColumns(); grid.deselectAllRows(); grid.clearCellSelection(); } - function onChange(e: IgrCheckboxChangeEventArgs) { + const onChange = (e: IgrCheckboxChangeEventArgs) => { const s = e.target as IgrCheckbox; + if (s.name === "allFeatures") { + const isChecked = e.detail.checked; + setAllOptions(isChecked); + setOption({ - cellSelection: e.detail.checked, - rowSelection: e.detail.checked, - filtering: e.detail.checked, - advancedFiltering: e.detail.checked, - paging: e.detail.checked, - sorting: e.detail.checked, - columns: e.detail.checked, - expansion: e.detail.checked, - rowPinning: e.detail.checked, - columnSelection: e.detail.checked, - rowIslands: e.detail.checked, + cellSelection: isChecked, + rowSelection: isChecked, + filtering: isChecked, + advancedFiltering: isChecked, + paging: isChecked, + sorting: isChecked, + columns: isChecked, + expansion: isChecked, + rowPinning: isChecked, + columnSelection: isChecked, + rowIslands: isChecked, }); - for (const key of Object.keys(options)) { - gridStateRef.current.options[key] = e.detail.checked; - } } else { - gridStateRef.current.options[s.name] = e.detail.checked; + const newOptions = { ...options }; + newOptions[s.name as keyof typeof newOptions] = e.detail.checked; + setOption(newOptions); } } - function leavePage() { + const leavePage = () => { saveGridState(); window.location.replace( "./grids/hierarchical-grid/state-persistence-about" ); } - function clearStorage() { + const clearStorage = () => { window.localStorage.removeItem(stateKey); } - function reloadPage() { + const reloadPage = () => { window.location.reload(); } @@ -281,9 +280,9 @@ export default function App() { moving={true} allowFiltering={true} allowAdvancedFiltering={true} - filterMode={FilterMode.ExcelStyleFilter} - columnSelection={GridSelectionMode.Multiple} - rowSelection={GridSelectionMode.Multiple} + filterMode="excelStyleFilter" + columnSelection="multiple" + rowSelection="multiple" > @@ -295,7 +294,13 @@ export default function App() { - + setPage(ev.detail)} + onPerPageChange={(ev) => setPerPage(ev.detail)}> + By default navigating to the previous page, components will reinitialize as per their initial configuration, therefore the IgrPivotGrid will lose its state.
- What our App Component does is reading the state from the window.localStorage object and applying the corresponding state in the `useEffect`` hook.
- Go Back + What our App Component does is reading the state from the window.localStorage object and applying the corresponding state in the `useEffect` hook.
+ Go Back diff --git a/samples/grids/pivot-grid/state-persistence-main/src/index.tsx b/samples/grids/pivot-grid/state-persistence-main/src/index.tsx index e8fd13516..935a38b3f 100644 --- a/samples/grids/pivot-grid/state-persistence-main/src/index.tsx +++ b/samples/grids/pivot-grid/state-persistence-main/src/index.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useRef, useState } from "react"; import ReactDOM from "react-dom/client"; import { - IgrGridModule, IgrGridState, IgrGridStateOptions, IgrPivotGrid, @@ -15,9 +14,7 @@ import { IgrButton, IgrCheckbox, IgrCheckboxChangeEventArgs, - IgrCheckboxModule, IgrIcon, - IgrIconModule, } from "igniteui-react"; import { registerIconFromText } from "igniteui-webcomponents"; import { PivotDataFlat } from "./PivotDataFlat"; @@ -26,9 +23,6 @@ import "igniteui-react-grids/grids/combined"; import "igniteui-react-grids/grids/themes/light/bootstrap.css"; import "./index.css"; -const mods: any[] = [IgrGridModule, IgrIconModule, IgrCheckboxModule]; -mods.forEach((m) => m.register()); - const restoreIcon = ''; const saveIcon = @@ -44,6 +38,8 @@ const refreshIcon = export default function App() { const gridData = new PivotDataFlat(); + const stateKey = "pivot-grid-state"; + const [allOptions, setAllOptions] = useState(true); const [options, setOption] = useState({ cellSelection: true, @@ -55,11 +51,30 @@ export default function App() { }); let grid: IgrPivotGrid; - function gridRef(ref: IgrPivotGrid) { + const gridRef = (ref: IgrPivotGrid) => { grid = ref; } - const stateKey = "pivot-grid-state"; - let gridStateRef = useRef(null); + const gridStateRef = useRef(null); + + const totalSale = (member: any, data: any) => { + return data.reduce( + (accumulator: any, value: any) => + accumulator + value.ProductUnitPrice * value.NumberOfUnits, + 0 + ); + } + + const totalMin = (member: any, data: any) => { + return data + .map((x: any) => x.ProductUnitPrice * x.NumberOfUnits) + .reduce((a: any, b: any) => Math.min(a, b)); + } + + const totalMax = (member: any, data: any) => { + return data + .map((x: any) => x.ProductUnitPrice * x.NumberOfUnits) + .reduce((a: any, b: any) => Math.max(a, b)); + } const pivotConfiguration: IgrPivotConfiguration = { // column dimensions @@ -145,19 +160,19 @@ export default function App() { }; }, []); - function saveGridState() { + const saveGridState = () => { const state = gridStateRef.current.getStateAsString([]); window.localStorage.setItem(stateKey, state); } - function restoreGridState() { + const restoreGridState = () => { const state = window.localStorage.getItem(stateKey); if (state) { gridStateRef.current.applyStateFromString(state, []); } } - function resetGridState() { + const resetGridState = () => { grid.allDimensions.forEach((dimension) => grid.clearFilter(dimension.memberName) ); @@ -166,7 +181,7 @@ export default function App() { grid.clearCellSelection(); } - function onValueInit(s: IgrPivotGrid, event: IgrPivotValueEventArgs) { + const onValueInit = (event: IgrPivotValueEventArgs) => { const value: IgrPivotValue = event.detail; if (value.member === "AmountofSale") { value.aggregate.aggregator = totalSale; @@ -191,58 +206,41 @@ export default function App() { } } - function onChange(e: IgrCheckboxChangeEventArgs) { + const onChange = (e: IgrCheckboxChangeEventArgs) => { const s = e.target as IgrCheckbox; + if (s.name === "allFeatures") { + const isChecked = e.detail.checked; + setAllOptions(isChecked); + setOption({ - cellSelection: e.detail.checked, - filtering: e.detail.checked, - sorting: e.detail.checked, - expansion: e.detail.checked, - columnSelection: e.detail.checked, - pivotConfiguration: e.detail.checked, + cellSelection: isChecked, + filtering: isChecked, + sorting: isChecked, + expansion: isChecked, + columnSelection: isChecked, + pivotConfiguration: isChecked, }); - for (const key of Object.keys(options)) { - gridStateRef.current.options[key] = e.detail.checked; - } } else { - gridStateRef.current.options[s.name] = e.detail.checked; + const newOptions = { ...options }; + newOptions[s.name as keyof typeof newOptions] = e.detail.checked; + setOption(newOptions); } } - function leavePage() { + const leavePage = () => { saveGridState(); window.location.replace("./grids/pivot-grid/state-persistence-about"); } - function clearStorage() { + const clearStorage = () => { window.localStorage.removeItem(stateKey); } - function reloadPage() { + const reloadPage = () => { window.location.reload(); } - function totalSale(member: any, data: any) { - return data.reduce( - (accumulator: any, value: any) => - accumulator + value.ProductUnitPrice * value.NumberOfUnits, - 0 - ); - } - - function totalMin(member: any, data: any) { - return data - .map((x: any) => x.ProductUnitPrice * x.NumberOfUnits) - .reduce((a: any, b: any) => Math.min(a, b)); - } - - function totalMax(member: any, data: any) { - return data - .map((x: any) => x.ProductUnitPrice * x.NumberOfUnits) - .reduce((a: any, b: any) => Math.max(a, b)); - } - return (
@@ -339,10 +337,10 @@ export default function App() { width="95%" height="500px" pivotConfiguration={pivotConfiguration} - valueInit={onValueInit} + onValueInit={onValueInit} superCompactMode={true} - columnSelection={GridSelectionMode.Single} - cellSelection={GridSelectionMode.Single} + columnSelection="single" + cellSelection="single" > diff --git a/samples/grids/tree-grid/state-persistence-about/src/index.tsx b/samples/grids/tree-grid/state-persistence-about/src/index.tsx index 1b676f6de..193e2027a 100644 --- a/samples/grids/tree-grid/state-persistence-about/src/index.tsx +++ b/samples/grids/tree-grid/state-persistence-about/src/index.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom/client'; -import { ButtonVariant, IgrButton } from 'igniteui-react'; +import { IgrButton } from 'igniteui-react'; import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; import './index.css'; @@ -17,8 +17,8 @@ export default function App() {
By default navigating to the previous page, components will reinitialize as per their initial configuration, therefore the IgrTreeGrid will lose its state.
- What our App Component does is reading the state from the window.localStorage object and applying the corresponding state in the `useEffect`` hook.
- Go Back + What our App Component does is reading the state from the window.localStorage object and applying the corresponding state in the `useEffect` hook.
+ Go Back
diff --git a/samples/grids/tree-grid/state-persistence-main/src/index.tsx b/samples/grids/tree-grid/state-persistence-main/src/index.tsx index cb38fe7c3..0d7234385 100644 --- a/samples/grids/tree-grid/state-persistence-main/src/index.tsx +++ b/samples/grids/tree-grid/state-persistence-main/src/index.tsx @@ -2,10 +2,8 @@ import React, { useEffect, useRef, useState } from "react"; import ReactDOM from "react-dom/client"; import { - FilterMode, IgrActionStrip, IgrColumn, - IgrGridModule, IgrGridPinningActions, IgrGridToolbar, IgrGridToolbarActions, @@ -14,16 +12,13 @@ import { IgrPaginator, IgrGridState, IgrGridStateOptions, - GridSelectionMode, IgrTreeGrid, } from "igniteui-react-grids"; import { IgrButton, IgrCheckbox, - IgrCheckboxModule, IgrCheckboxChangeEventArgs, IgrIcon, - IgrIconModule, } from "igniteui-react"; import { registerIconFromText } from "igniteui-webcomponents"; @@ -32,9 +27,6 @@ import "igniteui-react-grids/grids/themes/light/bootstrap.css"; import "./index.css"; import { EmployeesNestedData } from "./EmployeesNestedData"; -const mods: any[] = [IgrGridModule, IgrIconModule, IgrCheckboxModule]; -mods.forEach((m) => m.register()); - const restoreIcon = ''; const saveIcon = @@ -50,6 +42,8 @@ const refreshIcon = export default function App() { const gridData = new EmployeesNestedData(); + const stateKey = "tree-grid-state"; + const [allOptions, setAllOptions] = useState(true); const [options, setOption] = useState({ cellSelection: true, @@ -63,14 +57,15 @@ export default function App() { rowPinning: true, columnSelection: true, }); + const [page, setPage] = useState(0); + const [perPage, setPerPage] = useState(15); + const [totalRecords, setTotalRecords] = useState(gridData.length); let grid: IgrTreeGrid; - function gridRef(ref: IgrTreeGrid) { + const gridRef = (ref: IgrTreeGrid) => { grid = ref; } - let paginatorRef = useRef(null); - const stateKey = "tree-grid-state"; - let gridStateRef = useRef(null); + const gridStateRef = useRef(null); useEffect(() => { registerIconFromText("restore", restoreIcon, "material"); @@ -87,22 +82,23 @@ export default function App() { }; }, []); - function saveGridState() { + const saveGridState = () => { const state = gridStateRef.current.getStateAsString([]); window.localStorage.setItem(stateKey, state); } - function restoreGridState() { + const restoreGridState = () => { const state = window.localStorage.getItem(stateKey); if (state) { gridStateRef.current.applyStateFromString(state, []); } } - function resetGridState() { - paginatorRef.current.page = 0; - paginatorRef.current.perPage = 15; - paginatorRef.current.totalRecords = gridData.length; + const resetGridState = () => { + setPage(0); + setPerPage(15); + setTotalRecords(gridData.length); + grid.clearFilter(null); grid.sortingExpressions = []; grid.deselectAllColumns(); @@ -110,39 +106,42 @@ export default function App() { grid.clearCellSelection(); } - function onChange(e: IgrCheckboxChangeEventArgs) { + const onChange = (e: IgrCheckboxChangeEventArgs) => { const s = e.target as IgrCheckbox; + if (s.name === "allFeatures") { + const isChecked = e.detail.checked; + setAllOptions(isChecked); + setOption({ - cellSelection: e.detail.checked, - rowSelection: e.detail.checked, - filtering: e.detail.checked, - advancedFiltering: e.detail.checked, - paging: e.detail.checked, - sorting: e.detail.checked, - columns: e.detail.checked, - expansion: e.detail.checked, - rowPinning: e.detail.checked, - columnSelection: e.detail.checked, + cellSelection: isChecked, + rowSelection: isChecked, + filtering: isChecked, + advancedFiltering: isChecked, + paging: isChecked, + sorting: isChecked, + columns: isChecked, + expansion: isChecked, + rowPinning: isChecked, + columnSelection: isChecked, }); - for (const key of Object.keys(options)) { - gridStateRef.current.options[key] = e.detail.checked; - } } else { - gridStateRef.current.options[s.name] = e.detail.checked; + const newOptions = { ...options }; + newOptions[s.name as keyof typeof newOptions] = e.detail.checked; + setOption(newOptions); } } - function leavePage() { + const leavePage = () => { saveGridState(); window.location.replace("./grids/tree-grid/state-persistence-about"); } - function clearStorage() { + const clearStorage = () => { window.localStorage.removeItem(stateKey); } - function reloadPage() { + const reloadPage = () => { window.location.reload(); } @@ -267,9 +266,9 @@ export default function App() { moving={true} allowFiltering={true} allowAdvancedFiltering={true} - filterMode={FilterMode.ExcelStyleFilter} - columnSelection={GridSelectionMode.Multiple} - rowSelection={GridSelectionMode.Multiple} + filterMode="excelStyleFilter" + columnSelection="multiple" + rowSelection="multiple" > @@ -281,7 +280,13 @@ export default function App() { - + setPage(ev.detail)} + onPerPageChange={(ev) => setPerPage(ev.detail)}> +