Skip to content
kopecmi8 edited this page Mar 12, 2022 · 12 revisions
logo

Frui.ts is a frontend framework using MVVM design pattern for clean separation of concerns and long-term maintainability.

Frui.ts is family of indenpendent packages witch toghether creates full MVVM framework. Packages could could be also used alone, or some parts could be replaced by another package of your choise.

Friu-ts-packages-structure

It allows ViewModel-first approach, which enables automated testing of complex workflows on the ViewModel level with no need to simulate actual user interaction with the UI.

This framework is designed to support both small and large applications with SOLID codebase. It is built on top of the React library, using MobX and written in the modern TypeScript language.

You can read our business overview or jump to the documentation.

Why should you use Frui.ts?

Because you can write this:

model.ts - define your model or entities, define validation rules

interface ICustomer {
  id: number;
  firstName: string;
  lastName: string;
  categoryId: number;
}

const CustomerValidationRules = {
  firstName: { maxLength: 35 },
  lastName: { required: true, maxLength: 35 },
  categoryId: { required: true },
};

viewModel.ts - write only the code that actually makes sense

class CustomerViewModel {
  categories = [
    { id: 1, name: "Premium" },
    { id: 2, name: "Standard" },
  ];

  @observable customer: ICustomer;

  constructor(private customerId, private repository: ICustomersRepository) {}

  async onInitialize() {
    this.customer = await this.repository.loadCustomer(this.customerId);
    attachAutomaticValidator(this.customer, CustomerValidationRules);
  }

  get canSave() {
    return isValid(this.customer);
  }

  @action.bound
  async save() {
    await this.repository.updateCustomer(this.customerId, this.customer);
  }
}

view.tsx - declare how the VM should be presented

const customerView: ViewComponent<CustomerViewModel> = observer(({ vm }) => (
  <form onSubmit={preventDefault(vm.submit)}>
    {/* Note the two-way binding here, autocomplete works for 'target' and 'property' properties */}
    <Input target={vm.customer} property="firstName" label="First name" />
    <Input target={vm.customer} property="lastName" label="Last name" />
    <Select
      target={vm.customer}
      property="categoryId"
      label="Category"
      items={vm.categories}
      keyProperty="id"
      textProperty="name"
      mode="key"
    />

    {/* The button will be enabled/disabled as the input values change */}
    <Button type="submit" disabled={!vm.canSave}>
      Save
    </Button>
  </form>
));

registerView(customerView, CustomerViewModel);

Motivation

We were not happy with the currently popular event-sourced state management, and that logic is usually dependent on the presentation structure. This is mainly caused by the nature of the applications that we usually develop: data-driven admin solutions with server backends.

From our point of view, using event-sourced state management such as Redux for the presentation layer is usually quite complex, makes the application hard to reason about, and requires a significant amount of boilerplate code. For example, take a simple master-detail screen with an edit form and validation - the code necessary for this ordinary scenario was a big issue for us. That's why we embrace the MVVM model, where ViewModels handle the presentation state. Please note that we are not against event sourcing, especially on the backend side. That is an entirely different story!

Even though the application logic is usually separated from the presentation part, there are still many places where it leaks to the view code (e.g., navigation/routing, validation, etc.). As we see it, existing solutions seem only to fix the symptoms, not the root cause. The cause is the View-first approach. If you start with views, you need to handle navigation, application structure, and so on in views, which should not be their responsibility. That's why we wanted to start with ViewModels, thus model the application from the logical point of view, and just after that project it to the actual user interface.

Technical description

MVVM pattern heavily depends on data binding. One-way binding is quite usual in the web world, and we have decided to rely on MobX with its observables and computed values for this purpose. Frui.ts also brings some custom features to make two-way binding possible.

The View part of MVVM should work only as a projection of data presented by the ViewModel, and thus we were looking for a markup framework with declarative syntax and strong IDE support. It might be surprising, but we have chosen React. Mainly because it is widespread, has a strong community, there are many UI controls available, and it has great tooling support. However, React is used solely for the UI rendering part, there is no need for its advanced features such as Context, so it should be possible to replace it with any other framework.

Frui.ts is UI framework agnostic - you can use it with any UI framework (such as React Bootstrap or Material UI) that supports React. It is also independent of backend or API structure. However, it brings some build-in support for REST API and Bootstrap.

Examples