Starcounter.Uniform is a library that provides server-side helpers for using and managing uniform components.
Table of contents generated with markdown-toc
This package is available on nuget. You can get it there. To install with CLI run:
Install-Package Starcounter.Uniform
Requires Starcounter 2.4.0.7243 or later and .NET Framework 4.6.1.
This library provides helping method for a variety of uniform components and way to use it is slightly different for each one of them.
One of the components that Starcounter.Uniform
covers is uni-data-table
. It provides rich view of a data table which contain funcionalities like:
- Pagination or infinite scrolling.
- Sorting.
- Support for variety of column types.
More about uni-data-table
component you can read in Uniform.css readme for uni-data-table.
To cover most of the cases we provide default implementation of all needed components to generate data table structure. Although uni-data-table
implementation is fully customizable and users can reimplement all of its part to work as they want it to.
Let's say you want to add a data table with people information to your existing view-model PeopleManagement
. To do that:
- Add a property for the data table structure in your
PeopleManagement.json
file:
{
"DataTable": {}
}
- Create your data table row view-model. Properties in this view-model will correspond to columns in the data table:
{
"FirstName": "",
"LastName": "",
"Email$": ""
}
- In your
PeopleManagment.json.cs
add data table initialization withDataTableBuilder
.
this.DataTable = new DataTablePage();
dataTablePage.DataTable = new DataTableBuilder<DataTableRowViewModel>()
.WithDataSource(DbLinq.Objects<DataTableRowDataModel>())
.WithColumns(columns =>
columns
.AddColumn(b => b.FirstName, column => column.DisplayName("First Name").Sortable().Filterable())
.AddColumn(b => b.LastName, column => column.Sortable().DisplayName("Last Name"))
.AddColumn(b => b.Email, column => column.Filterable().Sortable().DisplayName("Email")))
.Build();
Note that you don't have to add a column for every property of the row view-model.
- Add
<uni-data-table>
component to your view:
<uni-data-table slot="uniformdocs/datatable-data-table"
provider="{{model.DataTable}}" auto-pagination>
</uni-data-table>
After those steps your container object in view view-model should be populated with our UniFormItem
view-model that uni-data-table
component will understand and work with.
You can find view-model structure used by uni-data-table
on its readme page.
DataTableBuilder
provides fluent API to easily create an instance of our UniDataTable view-model. To create its instance you have to provide view-model for your rows data. Methods that DataTableBuilder
provides are:
Method Name | Arguments | Description |
---|---|---|
WithDataSource |
IQueryable<TData> |
Specifies the data source for the table with given queryable as the original data to expose. |
WithDataSource |
IQueryable<TData> , Action<DataProviderBuilder<TData, TViewModel>> |
Specifies the data source for the table with given queryable as the original data to expose and configuration for the details of the data source. |
WithDataSource |
IFilteredDataProvider<TViewModel> |
Specifies the data source for the table. This method allows the developer to use custom implementation of IFilteredDataProvider . |
WithColumns |
Action<DataColumnBuilder<TViewModel>> |
Speficies the column structure of the table. |
WithInitialPageIndex |
int |
Specify the initial page index for the table. If this method is never called, the initial page index will be zero. |
WithInitialPageSize |
int |
Specify the initial page size for the table. If this method is never called, the initial page index will be 50. |
Build |
Initializes the UniDataTable view-model with specified data source/provider, columns, initial page size and initial page index. |
Sometimes, you want to add a column that has no property on its own or maybe its value is computed from multiple properties. You can do that. Just call AddColumn
specifying the name manually:
.AddColumn("FullName", column => column.Sortable().Filterable().DisplayName("Full name"))
If you want to support filtering and/or sorting by this column, you have to extend the default Filter
:
public class BookFilter : QueryableFilter<Book>
{
protected override IQueryable<Book> ApplyFilter(IQueryable<Book> data, Filter filter)
{
if (filter == null) throw new ArgumentNullException(nameof(filter));
if (filter.PropertyName == nameof(BookViewModel.Display))
{
// We used string.Compare method to ignore diacritics in the strings.
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
CompareOptions compareOptions = CompareOptions.IgnoreNonSpace;
return data.ToList().Where
(
book =>
string.Compare(book.Author, filter.Value, cultureInfo, compareOptions) == 0
||
string.Compare(book.Title, filter.Value, cultureInfo, compareOptions) == 0
).AsQueryable();
}
return base.ApplyFilter(data, filter);
}
protected override IQueryable<Book> ApplyOrder(IQueryable<Book> data, Order order)
{
if (order == null) throw new ArgumentNullException(nameof(order));
if (order.PropertyName == nameof(BookViewModel.Display))
{
IQueryable<Book> query;
if (order.Direction == OrderDirection.Ascending)
{
query = data.OrderBy(book => book.Author)
.ThenBy(book => book.Title);
}
else
{
query = data.OrderByDescending(book => book.Author)
.ThenByDescending(book => book.Title);
}
return query;
}
return base.ApplyOrder(data, order);
}
}
and then register it:
.WithDataSource(DbLinq.Objects<Book>(), data => data
.WithFilter(new BookFilter()))
Remember - you don't need need to make your custom properties filterable and/or sortable. For example, you can make it filterable (and override ApplyFilter
to support that), but not sortable (and skip overriding ApplyOrder
).
Sometimes, you want to control the creation of row view-models. You can do that with WithConverter
method.
First create your converter:
private BookViewModel CreateBookViewModel(Book book)
{
var bookViewModel = new BookViewModel()
{
DeleteAction = DeleteBook
};
((Json) bookViewModel).Data = book;
return bookViewModel;
}
and then register it:
.WithDataSource(DbLinq.Objects<Book>(), data => data
.WithConverter(CreateBookViewModel)
// In your page view model .cs file:
this.DataTable = new DataTableBuilder<BookViewModel>()
.WithDataSource(DbLinq.Objects<Book>(), data => data
.WithConverter(CreateBookViewModel)
.WithFilter(new BookFilter()))
.WithColumns(columns =>
columns
.AddColumn(b => b.Position, column => column
.Sortable()
.Filterable()
.DisplayName("no. "))
.AddColumn(b => b.Display, column => column
.Filterable()
.DisplayName("Author&Title"))
)
.Build();
private BookViewModel CreateBookViewModel(Book book)
{
var bookViewModel = new BookViewModel()
{
DeleteAction = DeleteBook
};
((Json) bookViewModel).Data = book;
return bookViewModel;
}
private void DeleteBook(BookViewModel bookViewModel)
{
bookViewModel.Data.Delete();
}
// BookFilter.cs
public class BookFilter : QueryableFilter<Book>
{
protected override IQueryable<Book> ApplyFilter(IQueryable<Book> data, Filter filter)
{
if (filter.PropertyName == nameof(BookViewModel.Display))
{
return data.Where(book => book.Author == filter.Value || book.Title == filter.Value);
}
return base.ApplyFilter(data, filter);
}
}
uni-data-table
is capable of handling view-models that are nested in the row view-model.
{
"FirstName": "",
"Email": {
"Address$": ""
},
}
To do so you have to register new column name for the nested property:
this.DataTable = new DataTablePage();
dataTablePage.DataTable = new DataTableBuilder<DataTableRowViewModel>()
.WithDataSource(DbLinq.Objects<DataTableRowDataModel>())
.WithColumns(columns =>
columns
.AddColumn(b => b.FirstName, column => column.DisplayName("Name"))
.AddColumn(b => b.Email.Address, column => column.DisplayName("Email")))
.Build();
and then add custom column display in the client side implementation with a proper reference to a nested property as a column value:
<input type="email" value="{{item.Email.Address$::input}}" placeholder="Email">
More about custom definition of the table columns you can find in the uni-data-table
CE documentation
Note that for the filtering and sorting to work you will have to add custom implementation of the QueryableFilter
. Example implementation of the custom filter and sorter you can find in the Computed columns paragraph.
Row view-models (like BookViewModel
in the example above) derive from Json
which means they all implement IDisposable
. Whenever the rows are refreshed, the old rows collection is disposed - i.e. all the row view-models have their Dispose
method called. When the UniDataTable
is disposed, all of its current row view-models have their Dispose
method called
uni-form-item
decorates an optional native <label>
, any native or custom form control element, and an optional <output>
message as form item. uni-form-item-group
groups multiple uni-form-item
elements into one form group with separate label and message.
You can read more about uni-form-item
and uni-form-item-group
components in Uniform.css readme for uni-form-item and readme for uni-form-item-group.
uni-form-item
/uni-form-item-group
helper part works based on FormItemMetadata
structure that is filled with proper view-model for given properties. Structure of this view-model looks like this:
"FormItemMetadata": {
"FieldName": {
"Message": "",
"Invalid": ""
}
}
To generate such structure, you have to use FormItemMessagesBuilder()
which provides following methods:
Method Name | Arguments | Description |
---|---|---|
ForProperty |
string |
Specifies the property to generate message structure for. |
ForProperties |
IEnumerable<string> |
Specifies the properties to generate message structures for. |
Build |
Initializes the FormItemMetadata for specified properties. |
After calling FormItemMessagesBuilder
Build()
method, you will end up with FormItemMetadata
view-model with message structure for each previously described property. It also contains API for managing messages for each property:
Method Name | Arguments | Description |
---|---|---|
SetMessage |
string , string , MessageType |
Sets the message of specified type and with specified text for the given property name. |
GetMessage |
string |
Returns the FormItemMessage object, which contains text and type of a message for the given property name. |
ClearMessage |
string |
Clears the message for the given property name. |
ClearAllMessages |
Clears all the messages. |
To start using the helper in your BookReservationPage
you have to:
- Add a property for the
FormItemMetadata
structure in yourBookReservationPage.json
file:
{
"FormItemMetadata": {}
}
- Set
InstanceType
for your newly added property toFormItemMetadata
:
static BookReservationPage()
{
DefaultTemplate.FormItemMetadata.InstanceType = typeof(FormItemMetadata);
}
- Initialize
FormItemMetadata
structure by specifing which properties should have proper metadata structure inFormItemMessagesBuilder
(note that you can give it any property name, even one that is not present in your.json
file.) and then calling itsBuild()
method:
public void Init()
{
this.FormItemMetadata = new FormItemMessagesBuilder().ForProperty(nameof(this.Title)).Build();
}
- Start using it!:
void Handle(Input.Title action)
{
if (action.Value == "A Song of Ice and Fire")
{
this.FormItemMetadata.SetMessage(
nameof(this.Title),
"This book is currently unavailable!",
MessageType.Invalid);
}
}
For detailed changelog, check Releases.
MIT