dsuryd / dotNetify

Simple, lightweight, yet powerful way to build real-time web apps.
https://dotnetify.net
Other
1.17k stars 164 forks source link

Is there way to use ViewModel objects as rows in a DataGrid element? #127

Closed bugged84 closed 5 years ago

bugged84 commented 6 years ago

Consider the customer form example from the docs.

...
<VMContext vm="CustomerForm">
    <Frame>
        <DataGrid id="Contacts" onSelect={this.handleSelect} enable={!edit} />
...
...
AddProperty("Contacts", customerRepository.GetAll().Select(customer => ToContact(customer)))
    .WithItemKey(nameof(Contact.Id))
    .WithAttribute(new DataGridAttribute
    {
        RowKey = nameof(Contact.Id),
        Columns = new DataGridColumn[] {
            new DataGridColumn(nameof(Contact.Name), "Name") { Sortable = true },
            new DataGridColumn(nameof(Contact.Phone), "Phone") { Sortable = true },
            new DataGridColumn(nameof(Contact.Address), "Address") { Sortable = true },
            new DataGridColumn(nameof(Contact.City), "City") { Sortable = true },
            new DataGridColumn(nameof(Contact.ZipCode), "ZipCode") { Sortable = true }
        },
        Rows = 5
    }.CanSelect(DataGridAttribute.Selection.Single, _selectedContact));
...

What if I want the row items to be updated in real-time for all users if any user updates one of the row items? Is there a way to make the row items a view model class instead of a POCO class such that changes are pushed to all clients?

dsuryd commented 6 years ago

It's possible to do what you describe, unfortunately not with the DataGrid element at the moment. The underlying library, react-data-grid, seems to have an issue with rendering change on a row when it's not the selected row.

What you can do is create your own grid, (you can use Cell elements, see example at http://www.dotnetify.net/elements/structure/cell).

You don't need to the row to be view model objects, just use the CRUD APIs with PushUpdates to update a row. When the view model inherits from MulticastVM, the update will be sent to all connections.

bugged84 commented 6 years ago

Ah, I see. I'll try that approach and post back here with the result. Great library by the way. I like the new elements addition.

bugged84 commented 5 years ago

BTW, the suggested CRUD APIs worked for this scenario.

bertemail commented 5 years ago

Consider the case:

RowData contains an Observable property Status, which is a Subject that receives notifications from a source observable that is outside of my app. How/Where should I use the CRUD APIs: this.UpdateList(propertyName, item) in order to push the update to each row in the view?

Thanks.

public class RowData
{
    public string Id { get; set; }    
    public IObservable<string> Status { get; set; }
}
public class RowService
{
    public IObservable<RowData> Rows {get;}
}

View models

public class RowList : BaseVM
{
    public string Records_itemKey => nameof(RowData.Id);
    public ReactiveProperty<Row[]> Rows = new ReactiveProperty<Row[]>();

    public RowList(RecordService recordService)
    {
    Rows.SubscribeTo(rowService.Rows.Select(value =>
    {
            var records= new Queue<Row>(Get<Row[]>("Rows")?.Reverse() ?? new Row[] { });
        records.Enqueue(new Row(value));

        return records.ToArray();
    })).SubscribedBy(AddInternalProperty<bool>("Update"), _ =>
    {
        PushUpdates();
        return true;
    });
   }
}
public class Row : BaseVM
{
    RowData _record;
    public ReactiveProperty<string> Status= new ReactiveProperty<string>();

    public Row(RowData record)
    {
        _record = record;

        Status.SubscribeTo(record.Status)
        .SubscribedBy(AddInternalProperty<bool>("Update"), _ =>
        {
            PushUpdates();
            return true;
        });
    }
}

Views

export default class RowList extends React.Component<Props, State> {
    dispatch: (state: any) => dotnetifyVM;
    vm: any;
    constructor(props) {
        ...
        this.vm = dotnetify.react.connect("RowList", this);
        ...
    }
    render() {
...
        return (
            ...
                <RowListTable data={Records} />
            ...
        );
    }
}
export default class RowListTable extends React.Component<Props, any> {
    render() {
        const records = this.props.data;
        return (
            <Table>
                             ...
                <TableBody>
                    {records.map(item => (
                    <TableRow key={item.Id}>
                        <TableCell>{item.Status}</TableCell>
                        ...
                    </TableRow>
                    ))}
                </TableBody>
         </Table >      
        );
    }
}
dsuryd commented 5 years ago

There's no need for Row to inherit from BaseVM; instead, subscribe to Status from RowList:

public class RowList : BaseVM
{
    public string Rows_itemKey => nameof(RowData.Id);

    public RowList(RecordService recordService)
    {
    AddProperty<Row[]>("Rows").SubscribeTo(rowService.Rows.Select(value =>
    {
            var records = new Queue<Row>(Get<Row[]>("Rows")?.Reverse() ?? new Row[] { });
        records.Enqueue(new Row(value));

                value.Status.Subscribe(newStatus => 
                { 
                   this.UpdateList("Rows", new Row(value, newStatus));
                   PushUpdates();
                });

        return records.ToArray();
    })).SubscribedBy(AddInternalProperty<bool>("Update"), _ =>
    {
        PushUpdates();
        return true;
    });
   }
}
bertemail commented 5 years ago

Thanks it works!