Skip to content
Olmo edited this page Jan 16, 2017 · 51 revisions

Signum React

The future (or present?) of web development is Single Page Applications, and React, from Facebook, looks like the most promising alternative.

What I like about SPA in general:

  • Fast and fluid UI
  • Client code is more understandable, because you're sending (JSON) entities back and forth, not pieces of HTML. This will make it easier to get out of the box.

What I like about React:

  • It's small an unopinionated, reducing the surface of conflict with Signum Framework.
  • There are less things to learn because the templating engine is Javascript. It's also super-expressive since the updates are just re-draws.
  • Using TypeScript 1.6 and JSX support even the view <-> model bindings are strongly typed, this is a killer feature that sets react apart from any other UI framework (Angular, Aurelia... WPF).
  • Can be rendered in the server as well, important for SEO for public catalogs etc...

Solution

The key of the invention is to replicate automatically most of the vocabulary expressed in the Entities assemblies (Entity properties, operations, messages, query names, etc...) so that when writing react components and interaction workflows in typescript you feel at home. The experience could be even better than in XAML, since JSX in Typescript is a powerful strongly typed language, better than XAML.

Links to the base technologies:

And polyfils:

Challenges

React promotes a unidirectional model-to-view binding and then use old-school events to update the model and redraw the control. This is efficient because only the virtual DOM is re-drawn (fast) and then compared with the real DOM (slow) to apply the minimal changes possible.

The default way in order to tell React when the view has changed is to use setState and primitive types. This will work for many cases but not for binding big entity graphs. Fortunately forceUpdate is also available for this options.

Also, TypeScript 1.6 is released and while the compiler support for JSX is good, the VS tooling lacks some polishing. They are working on it so should be solved before Signum.React is ready.

Early Example

Using C#/Asp.Net MVC/Razor (old)

@using Signum.Entities.Basics
@using (var e = Html.TypeContext<ExceptionEntity>())
{
    e.LabelColumns = new BsColumn(4);
    <div class="row">
        <div class="col-sm-6">
            @Html.ValueLine(e, f => f.Environment)
            @Html.ValueLine(e, f => f.CreationDate, vl => vl.UnitText = e.Value.CreationDate.ToUserInterface().ToAgoString())
            @Html.EntityLine(e, f => f.User)
            @Html.ValueLine(e, f => f.Version)
            @Html.ValueLine(e, f => f.ThreadId)
            @Html.ValueLine(e, f => f.MachineName)
            @Html.ValueLine(e, f => f.ApplicationName)
        </div>
        <div class="col-sm-6">
            @Html.ValueLine(e, f => f.ActionName)
            @Html.ValueLine(e, f => f.ControllerName)
            @Html.ValueLine(e, f => f.UserHostAddress)
            @Html.ValueLine(e, f => f.UserHostName)
            @Html.ValueLine(e, f => f.UserAgent, vl => vl.ValueLineType = ValueLineType.TextArea)
        </div>
    </div>
    e.LabelColumns = new BsColumn(2);
    @Html.ValueLine(e, f => f.RequestUrl)
    @Html.ValueLine(e, f => f.UrlReferer)
}

Using TypeScript/JSX/React (new)

/// <reference path="../../framework/signum.react/typings/react/react.d.ts" />

import * as React from 'react'
import { Basics } from 'framework/signum.react/Scripts/Signum.Entities'
import { EntityComponent, ValueLine, EntityLine, ValueLineType } from 'framework/signum.react/Scripts/Lines'

export class Exception extends EntityComponent<Basics.ExceptionEntity> {
    render() {
        var e = this.subContext(a=> a, { labelColumns: { sm: 4 } });
        return (
<div>
    <div className="row">
        <div className="col-sm-6">
            <ValueLine ctx={e.subCtx(f => f.environment) } />
            <ValueLine ctx={e.subCtx(f => f.creationDate) }
                unitText={this.value.creationDate.toUserInterface().toAgoString() } />
            <EntityLine ctx={e.subCtx(f => f.user) } />
            <ValueLine ctx={e.subCtx(f => f.version) } />
            <ValueLine ctx={e.subCtx(f => f.threadId) } />
            <ValueLine ctx={e.subCtx(f => f.machineName) } />
            <ValueLine ctx={e.subCtx(f => f.applicationName) } />
        </div>
        <div className="col-sm-6">
            <ValueLine ctx={e.subCtx(f => f.actionName) } />
            <ValueLine ctx={e.subCtx(f => f.controllerName) } />
            <ValueLine ctx={e.subCtx(f => f.userHostAddress) } />
            <ValueLine ctx={e.subCtx(f => f.userHostName) } />
            <ValueLine ctx={e.subCtx(f => f.userAgent) } valueLineType={ValueLineType.TextArea} />
            </div>
        </div>
    <ValueLine ctx={this.subContext(f => f.requestUrl) } />
    <ValueLine ctx={this.subContext(f => f.urlReferer) } />
</div>);
    }
}

TODO

At least for me, it's clear that the new UI with react is the way forward, but re-implementing everything is not going to be easy. Any help will be appreciated 😃

Framework:

  • Create entity proxies in Typescript using T4
    • Consider splitting files per namespace instead of assembly --> NO
  • Serialize/deserialize entities in JSON using Json.Net
    • Mappings needed?
  • Investigate Web.Api session.
  • Expose entities metadata to TypeScript (Types, Translations, UI Permissions)
    • Web.Api controllers
    • TypeScript classes
  • TypeContext and EntityComponent
    • Solution for ViewOverrides
  • EntityLines
    • ValueLine
    • EntityLine
    • EntityDetail
    • EntityCombo
    • EntityList
    • EntityStrip
    • EntityRepeater
    • EntityCheckBoxList
  • Operations
    • Construct
    • ConstructFrom
    • ConstructFromMany
    • Exceute
    • Delete
  • DynamicQueries
    • TypeScript definitions (FindOptions, ResultTable, QueryToken, etc...)
    • Create Web.Api controllers
    • QueryTokenBuilder
    • FilterBuilder
    • SearchControl
      • Pagination
      • Add/remove/rename columns
      • Column/row styling
    • CountSearchControl
  • Signum.Utilities in TS as we need it
    • LINQ stuff (groupBy, distinct, etc..)
    • translation, to ago string, etc...
    • string extensions
  • Code Generation

Extensions:

  • Authorization
    • Login / Remember password / etc..
    • Extension points
    • Admin rules
  • Charting
    • ChartRequest
    • UserChart
    • ChartScript
  • UserQueries
  • Dashboard
    • GridRepater
    • Dashboard edit
    • Dashboard view
  • Omnibox
  • Map
    • Schema
    • Operations
  • Excel
  • Cache
  • Profiler
  • Files
    • FileLine
  • Processes
  • Scheduler
  • Word
  • Mailing
    • Templates
    • [50%] Messages
    • Async email sender
    • Pop3
    • Newsletter
  • Translations
    • Code Translations View / Sync
    • Entity Translation View / Sync
  • Selenium React Test
    • Lines
    • Operations
    • SearchControl

Extensions Posponed / On Demand

  • Help
  • SMS
  • Notes
  • Alerts
  • Disconnected
  • Isolation

Current Issues

Here a list of the current issues that need improvement:

  • Webpack is WAY too slow, specially in watch mode (maybe 20s for large apps)
  • Webpack watch mode is unreliable in VS
  • EntityComponent<T> / EntityComponentWithState<T> is a dead end whenever you need state or new properties, should be just a React.Component<{ctx : TypeContext<T>}, void>.
  • React-router baseUrl is a hack. Implement one using ~/appLocalUrl syntax as in ASP.Net

Installation

Cheat Sheet

c# Typescript
enum OrderState { Ordered, Cancelled } `type OrderState = "Ordered"
class OrderEntity : Entity interface OrderEntity exends Entity
OrderEntity o; var o : OrderEntity;
o.OrderNumber o.orderNumber
o.ToString() getToString(o)
o.GetType() == typeof(OrderEntity) o.Type == OrderEntity.TypeName
o.State = OrderState.Ordered o.state = "Ordered"
o.TotalAmount.ToString("0.0") numbro(o.totalAmount).format("0.0")
o.CreationDate = DateTime.Now o.creationDate = moment().format()
@Html.ValueLine(ctx, o =>o.CreationDate) <ValueLine ctx={ctx.subCtx(o=>o.creationDate)} />
OrderState.Ordered.NiceToString() OrderState.niceName("Ordered")
typeof(OrderEntity).NiceName() OrderEntity.niceName()
OrderMessage.Please.NiceToString() OrderMessage.Please.niceToString()

Style Guide

We're using https://github.com/Khan/style-guides/blob/master/style/react.md

Webpack + MSBuild (TeamCity)

Include this in YourApp.React.csproj in order to make it work in TeamCity.

You'll also need to instal node in the TeamCity server

<UsingTask TaskName="SignumTSGenerator" AssemblyFile="..\Framework\Signum.TSGenerator\Binaries\Signum.TSGenerator.dll" />
  <Target Name="GenerateSignumTS">
    <SignumTSGenerator References="@(ReferencePath)" />
  </Target>
  <PropertyGroup>
    <CompileTypeScriptDependsOn>
      GenerateSignumTS;
      $(CompileTypeScriptDependsOn);
    </CompileTypeScriptDependsOn>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' != 'Debug|AnyCPU'">
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
    <CompileDependsOn>
      $(CompileDependsOn);
      WebPackBuild;
    </CompileDependsOn>
  </PropertyGroup>
  <Target Name="WebPackBuild" DependsOnTargets="CompileTypeScript">
    <Exec Command="npm install" />
    <Exec Command="npm run build" />
  </Target>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' != 'Debug|AnyCPU'">
    <CopyAllFilesToSingleFolderForPackageDependsOn>
      $(CopyAllFilesToSingleFolderForPackageDependsOn);
      CollectWebpackOutput;
    </CopyAllFilesToSingleFolderForPackageDependsOn>
    <CopyAllFilesToSingleFolderForMsdeployDependsOn>
      $(CopyAllFilesToSingleFolderForMsdeployDependsOn);
      CollectWebpackOutput;
    </CopyAllFilesToSingleFolderForMsdeployDependsOn>
  </PropertyGroup>
  <Target Name="CollectWebpackOutput">
    <ItemGroup>
      <_CustomFiles Include="dist\**\*" />
      <FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
        <DestinationRelativePath>dist\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
      </FilesForPackagingFromProject>
    </ItemGroup>
    <Message Text="CollectWebpackOutput list: %(_CustomFiles.Identity)" />
  </Target>
</Project>