Skip to content
Open
20 changes: 20 additions & 0 deletions src/examples/src/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ import ListItemRenderer from './widgets/list/ListItemRenderer';
import FetchedResource from './widgets/list/FetchedResource';
import DisabledList from './widgets/list/Disabled';
import DraggableList from './widgets/list/Draggable';
import StaticOptionList from './widgets/list/StaticOption';
import CustomThemeList from './widgets/list/CustomTheme';
import Menu from './widgets/list/Menu';
import CustomTransformer from './widgets/list/CustomTransformer';
Expand Down Expand Up @@ -161,6 +162,7 @@ import RequiredRangeSlider from './widgets/range-slider/Required';
import LabelledRangeSlider from './widgets/range-slider/Labelled';
import ControlledRangeSlider from './widgets/range-slider/Controlled';
import AdditionalText from './widgets/select/AdditionalText';
import PlaceholderSelect from './widgets/select/Placeholder';
import BasicSelect from './widgets/select/Basic';
import ControlledSelect from './widgets/select/Controlled';
import CustomRenderer from './widgets/select/CustomRenderer';
Expand Down Expand Up @@ -254,6 +256,7 @@ import PaginationPageSizeSelector from './widgets/pagination/PageSizeSelector';
import PaginationControlled from './widgets/pagination/Controlled';
import PaginationSiblingCount from './widgets/pagination/SiblingCount';
import BasicTypeahead from './widgets/typeahead/Basic';
import PlaceholderTypeahead from './widgets/typeahead/Placeholder';
import RemoteTypeahead from './widgets/typeahead/RemoteSource';
import ValidatedTypeahead from './widgets/typeahead/Validation';
import FreeTextTypeahead from './widgets/typeahead/FreeText';
Expand Down Expand Up @@ -1147,6 +1150,11 @@ export const config = {
module: FillList,
title: 'Fill'
},
{
filename: 'StaticOption',
module: StaticOptionList,
title: 'Static Option'
},
{
description: 'This example shows how list items can be easily themed',
filename: 'CustomTheme',
Expand Down Expand Up @@ -1574,6 +1582,13 @@ export const config = {
sandbox: true,
size: 'medium'
},
{
filename: 'Placeholder',
module: PlaceholderSelect,
title: 'Placeholder',
sandbox: true,
size: 'medium'
},
{
filename: 'DisabledSelect',
module: DisabledSelect,
Expand Down Expand Up @@ -2090,6 +2105,11 @@ export const config = {
sandbox: true,
size: 'large'
},
{
filename: 'Placeholder',
module: PlaceholderTypeahead,
title: 'Placeholder'
},
{
filename: 'Controlled',
module: ControlledTypeahead,
Expand Down
22 changes: 22 additions & 0 deletions src/examples/src/widgets/list/StaticOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { create, tsx } from '@dojo/framework/core/vdom';
import List from '@dojo/widgets/list';
import icache from '@dojo/framework/core/middleware/icache';
import Example from '../../Example';
import { listOptionTemplate } from '../../template';

const factory = create({ icache });

export default factory(function StaticOption({ middleware: { icache } }) {
return (
<Example>
<List
resource={{ template: listOptionTemplate }}
onValue={(value) => {
icache.set('value', value);
}}
staticOption={{ value: 'static', label: 'This is a static option' }}
/>
<p>{`Clicked on: ${JSON.stringify(icache.getOrSet('value', ''))}`}</p>
</Example>
);
});
11 changes: 5 additions & 6 deletions src/examples/src/widgets/select/AdditionalText.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { create, tsx } from '@dojo/framework/core/vdom';
import Select from '@dojo/widgets/select';
import icache from '@dojo/framework/core/middleware/icache';
import Example from '../../Example';
import {
createResourceTemplate,
createResourceMiddleware
createResourceMiddleware,
createResourceTemplate
} from '@dojo/framework/core/middleware/resources';
import { create, tsx } from '@dojo/framework/core/vdom';
import { ListOption } from '@dojo/widgets/list';
import Select from '@dojo/widgets/select';
import Example from '../../Example';

const resource = createResourceMiddleware();
const factory = create({ icache, resource });
Expand All @@ -27,7 +27,6 @@ export default factory(function AdditionalText({ id, middleware: { icache, resou
icache.set('value', value);
}}
helperText="I am the helper text"
placeholder="I am a placeholder"
>
{{
label: 'Additional Text'
Expand Down
41 changes: 41 additions & 0 deletions src/examples/src/widgets/select/Placeholder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import icache from '@dojo/framework/core/middleware/icache';
import {
createResourceMiddleware,
createResourceTemplate
} from '@dojo/framework/core/middleware/resources';
import { create, tsx } from '@dojo/framework/core/vdom';
import { ListOption } from '@dojo/widgets/list';
import Select from '@dojo/widgets/select';
import Example from '../../Example';

const resource = createResourceMiddleware();
const factory = create({ icache, resource });
const options = [
{ value: '1', label: 'cat' },
{ value: '2', label: 'dog' },
{ value: '3', label: 'fish' }
];

const template = createResourceTemplate<ListOption>('value');

export default factory(function Placeholder({ id, middleware: { icache, resource } }) {
return (
<Example>
<Select
resource={resource({ template: template({ id, data: options }) })}
onValue={(value) => {
icache.set('value', value);
}}
placeholder={{
value: 'placeholder',
label: 'I am a placeholder'
}}
>
{{
label: 'Additional Text'
}}
</Select>
<pre>{JSON.stringify(icache.getOrSet('value', ''))}</pre>
</Example>
);
});
60 changes: 60 additions & 0 deletions src/examples/src/widgets/typeahead/Placeholder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { create, tsx } from '@dojo/framework/core/vdom';
import icache from '@dojo/framework/core/middleware/icache';
import Typeahead from '@dojo/widgets/typeahead';
import Example from '../../Example';
import {
createResourceTemplate,
createResourceMiddleware,
defaultFilter
} from '@dojo/framework/core/middleware/resources';
import { largeListOptions } from '../../data';
import { ListOption } from '@dojo/widgets/list';

const resource = createResourceMiddleware();
const factory = create({ icache, resource });

const dataWithDisabled = largeListOptions.map((item) => ({
...item,
disabled: Math.random() < 0.1
}));

export const listOptionTemplate = createResourceTemplate<ListOption>({
idKey: 'value',
read: async (req, { put }) => {
// emulate an async request
await new Promise((res) => setTimeout(res, 1000));
const { offset, size, query } = req;
const filteredData = dataWithDisabled.filter((item) => defaultFilter(query, item));
put({ data: filteredData.slice(offset, offset + size), total: filteredData.length }, req);
}
});

export default factory(function Placeholder({ middleware: { icache, resource } }) {
const strict = icache.getOrSet('strict', true);
return (
<Example>
<Typeahead
strict={strict}
resource={resource({
template: listOptionTemplate
})}
onValue={(value) => {
icache.set('value', value);
}}
placeholder={{
value: 'placeholder',
label: 'This is a placeholder'
}}
required
>
{{
label: 'Placeholder Typeahead'
}}
</Typeahead>
<button onclick={() => icache.set('strict', (strict = true) => !strict)}>
{strict ? 'Non strict' : 'strict'}
</button>
<pre>{JSON.stringify(icache.getOrSet('value', ''))}</pre>
</Example>
);
});
42 changes: 29 additions & 13 deletions src/list/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { RenderResult } from '@dojo/framework/core/interfaces';
import { focus } from '@dojo/framework/core/middleware/focus';
import dimensions from '@dojo/framework/core/middleware/dimensions';
import { focus } from '@dojo/framework/core/middleware/focus';
import { createICacheMiddleware } from '@dojo/framework/core/middleware/icache';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
import { throttle } from '@dojo/framework/core/util';
import { create, tsx } from '@dojo/framework/core/vdom';
import { Keys, isRenderResult } from '../common/util';
import theme, { ThemeProperties } from '../middleware/theme';
import { isRenderResult, Keys } from '../common/util';
import Icon from '../icon';
import LoadingIndicator from '../loading-indicator';
import offscreen from '../middleware/offscreen';
import theme, { ThemeProperties } from '../middleware/theme';
import * as listItemCss from '../theme/default/list-item.m.css';
import * as menuItemCss from '../theme/default/menu-item.m.css';
import * as css from '../theme/default/list.m.css';
import * as menuItemCss from '../theme/default/menu-item.m.css';
import * as fixedCss from './list.m.css';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
import LoadingIndicator from '../loading-indicator';
import { throttle } from '@dojo/framework/core/util';
import Icon from '../icon';

export interface MenuItemProperties {
/** Callback used when the item is clicked */
Expand Down Expand Up @@ -243,6 +243,8 @@ export interface ListProperties {
disabled?: (item: ListOption) => boolean;
/** Specifies if the list height should by fixed to the height of the items in view */
height?: 'auto' | 'fixed';
/** Static option to always show */
staticOption?: ListOption;
}

export interface ListChildren {
Expand Down Expand Up @@ -411,6 +413,7 @@ export const List = factory(function List({
}

function renderItems(start: number, count: number) {
const { staticOption } = properties();
const renderedItems = [];
const { size: resourceRequestSize } = options();
const {
Expand All @@ -434,6 +437,11 @@ export const List = factory(function List({
}
return get({ ...options(), offset: (page - 1) * options().size }, { read });
});
if (staticOption !== undefined) {
const { value, label, disabled, divider } = staticOption;
renderedItems[0] = renderItem({ value, label, disabled, divider }, -1);
}
const offset = renderedItems.length;
for (let i = 0; i < Math.min(total - start, count); i++) {
const index = i + startNode;
const page = Math.floor(index / resourceRequestSize) + 1;
Expand All @@ -442,16 +450,19 @@ export const List = factory(function List({
const items = pageItems[pageIndex];
if (items && items[indexWithinPage]) {
const { value, label, disabled, divider } = items[indexWithinPage];
renderedItems[i] = renderItem({ value, label, disabled, divider }, index);
renderedItems[i + offset] = renderItem(
{ value, label, disabled, divider },
index
);
} else if (!items) {
renderedItems[i] = renderPlaceholder(index);
renderedItems[i + offset] = renderLoading(index);
}
}
}
return renderedItems;
}

function renderPlaceholder(index: number) {
function renderLoading(index: number) {
const itemProps = {
widgetId: `${idBase}-item-${index}`,
key: `item-${index}`,
Expand Down Expand Up @@ -646,7 +657,8 @@ export const List = factory(function List({
return divider ? [item, <hr classes={themedCss.divider} />] : item;
}

let { value: selectedValue, draggable, onMove } = properties();
const { draggable, onMove, staticOption } = properties();
let { value: selectedValue } = properties();

if (selectedValue === undefined) {
if (initialValue !== undefined && initialValue !== icache.get('initial')) {
Expand Down Expand Up @@ -734,7 +746,7 @@ export const List = factory(function List({
const renderedItemsCount = calculatedItemsInView + 2 * nodePadding;
let computedActiveIndex = activeIndex === undefined ? icache.get('activeIndex') : activeIndex;
const inputText = icache.get('inputText');
const {
let {
meta: { total = 0 }
} = get(options(), { meta: true, read });
if (inputText && inputText !== icache.get('previousInputText') && total) {
Expand Down Expand Up @@ -791,6 +803,10 @@ export const List = factory(function List({
const offsetY = startNode * itemHeight;

const items = renderItems(startNode, renderedItemsCount);

if (staticOption !== undefined) {
total++;
}
const totalContentHeight = total * itemHeight;
return (
<div
Expand Down
44 changes: 44 additions & 0 deletions src/list/tests/List.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,50 @@ describe('List', () => {
r.expect(listWithListItemsAssertion);
});

it('should render list with static option', () => {
const r = renderer(
() => (
<List
resource={{ data, id: 'test', idKey: 'value' }}
onValue={onValueStub}
staticOption={{ value: 'staticOption', label: 'This is a static option' }}
/>
),
{ middleware: [[getRegistry, mockGetRegistry]] }
);
r.expect(
listWithListItemsAssertion
.setProperty(WrappedItemWrapper, 'styles', {
height: '180px'
})
.prepend(WrappedItemContainer, () => [
<ListItem
classes={undefined}
variant={undefined}
active={false}
disabled={false}
key={'item--1'}
onRequestActive={noop}
onSelect={noop}
selected={false}
theme={listItemTheme}
widgetId={'menu-test-item--1'}
collapsed={false}
draggable={undefined}
dragged={false}
movedDown={false}
movedUp={false}
onDragEnd={noop}
onDragOver={noop}
onDragStart={noop}
onDrop={noop}
>
This is a static option
</ListItem>
])
);
});

it('should render list with auto height', () => {
const r = renderer(
() => (
Expand Down
Loading