npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@nyaf/forms

v0.7.4

Published

An extension to "Not Yet Another Framework". A simple forms validation library.

Downloads

27

Readme

Version License

Introducing @nyaf

The name @nyaf is an acronym for "Not Yet Another Framework". It is, in fact, an entirely new concept of Web development support libraries, a so called "thin library".

It's simple, has a flat learning curve, doesn't need any special tools. Keep your tool chain, get the power. It can replace all the complex stuff such as React or Angular entirely, indeed.

Preface

This is an extension to the famous micro framework ny@f. You need to build your project on top of ny@f, then @nyaf/forms makes your life easier, again.

@nyaf Forms Module

Forms provides these basic features:

  • UI control decorators (example: @Hidden() to suppress a property in a dynamic table).
  • Validation decorators (example: @MaxLength(50) or @Required() to manage form validation).
  • Data Binding using a model declaration decorator called @ViewModel and a bind property named n-bind.

Form validation is a key part of any project. However, CSS frameworks require different strategies to handle errors and so on. Hence, the @nyaf/forms library provides a simple way (just like a skeleton) to give you the direction, but the actual validation implementation logic is up to you to build.

Same for the UI decorators. It's a convenient way to add hidden properties to viewmodels. There is no logic to read these values, this is up to you to implement this. However, the decorators makes your life a lot easier.

The binding logic is almost complete and once you have a decorated model it's syncing the UI automagically.

How it works

First, you need view models. Then, you decorate the properties with validation and hint decorators.

A view model could look like this:

export class UserViewModel {

  @Hidden()
  id: Number = 0;

  @Display('E-Mail', 'E-Mail address')
  @Required()
  @MaxLength(100)
  @Email()
  email: string = '';

  @Display('Phone Number', 'The user\'s phone')
  @Required('Please, the phone number is required')
  @MaxLength(20)
  phoneNumber: string = '';

  @Display('User Name', 'The full name')
  @Required()
  @MaxLength(100)
  userName: string = '';

}

The last (optional) parameter of the validation decorators is a custom error message.

Validation Decorators

| Decorator | Usage | |-----------|-------| |@MaxLength| The maximum length of a text input. | |@MinLength| The minimum length of a text input. | |@Pattern| A regular expression that is used to test the text or number input.| |@Range| A range (from-to) for either numerical values or dates. | |@Required| Makes the field mandatory. | |@EMail| Checks input against a (very good) regular expression to test for valid e-mail pattern.| |@Compare| Compares with another field, usually for password comparison.|

UI Decorators (property level)

| Decorator | Usage | |-----------|-------| |@Display| Determine the label's name and a tooltip ( optionally). | |@DisplayGroup| Groups components in <fieldset> elements. Can be ordered inside the form. | |@Hidden| Makes an hidden field. | |@Sortable| Makes a column sortable in table views. | |@Filterable| Adds a filter in table views. | |@Placeholder| A watermark that appears in empty form fields| |@ReadOnly| Forces a particular render type. Usually you get fields a shown in the table below. With a hint you can force other types.| |@TemplateHint| What kind of field (text, number, date, ...) and additional styles or classes. | |@Translate| For i18n of components |

Template language

ny@f has a simple template language. For forms it's just one for any input element:

model: ModelBinder<UserViewModel>; // instance created by decorator

async render() {
  return await (
    <>
      <form>
        <input n-bind="value: Name" />
      </form>
    </>);
}

Now the field knows everything about how to render and how to validate. The first item ("value") is the HTML element's property you bind to. The second is the model's property name ("Name").

Once you retrieve the DOM object, you get access to validation:

const username = this.querySelector('[name="userName"]');
if (username.valid){
  // call server
}
if (username.touched){
  // show help
}

For a good UI you need a label usually:

<label n-bind="innerText: userName" />

Forms

The model is provided by a decorator

@ViewModel(ModelType)
export class component extends BaseComponent<any> implements IModel<ModelType> {

  async render() {
    return await (
      <form>
        <label n-bind="innerText: userName" for="un"/>
        <input n-bind="value: userName" id="un" />
        <br />
        <label n-bind="innerText: city" for="city"/>
        <input n-bind="value: city" id ="city" />
     </form>
    )
  }

}

Forms bind data. It's bi-directional or uni-directional depending on the chosen handler.

Access the data

From code it's also possible:

const value = this.model.scope.property;
this.model.scope.property = 'another value';

The proxy object is available through scope. Binding is dependent on binding handler (bi- or uni-directional).

Validation

The error message is just regular output (class example from Bootstrap,not needed by nyaf forms):

<form>
  <label n-bind="innerText: userName" for="un"/>
  <input n-bind="value: userName" id="un">
  <div class="text text-danger" n-if={[email protected] && @userName.touched}>Oops, something got wrong</div>
</form>

Again, note the @ signs preceding the property names.

Validators can provide the error text, too:

<form>
  <label n-bind="innerText: userName" for="un"/>
  <input n-bind="value: userName" id="un">
  <div class="text text-danger"
       n-if={this.model.getScope().userName.valid && this.model.getScope().userName.touched"
       innerHTML={this.model.getScope().userName.errors} ></div>s
</form>

Distinguish between different validators like this:

<form>
  <label n-bind="innerText: userName" for="un"/>
  <input n-bind="value: userName" id="un">
  <div class="text text-danger"
       n-if={this.model.getScope().userName.valid && this.model.getScope().userName.touched"
       innerHTML={this.model.getScope().userName.errors.required.error} ></div>s
</form>

Objects are always set (not undefined), so you don't must test first. The property names are same as the decorators, but in lower case:

  • @MaxLength: maximum
  • @MinLength: minimum
  • @Pattern: pattern
  • @Range: range
  • @Required: required
  • @EMail: email
  • @Compare: compare

View Models in Components

For a nice view decorators applied to class properties control the appearance. Use FormsComponent as new base class (instead of BaseComponent). Use the decorator @ViewModel<T>(T) to define the model. The generic is the type, the constructor parameter defines the default values (it's mandatory).

export class Model {
  @Hidden()
  id: number = 0;

  @Required()
  name: string = '';
}

@CustomElement('app-main')
@Properties<{ data: Model }>()
@ViewModel<Model>(Model)
export class MainComponent extends FormsComponent {
  // ... omitted for brevity
}

Within the component, this is now present.

this.modelState = {
  isValid: boolean,
  isPresent: boolean,
  errors: { [key: string]: string },
  model: Model
}

It's supervised. After render this.modelState helds the state of the model.

Smart Binders

There is an alternative syntax that provides full type support:

<label n-bind={to<ContactModel>(c => c.email, 'innerText', Display)}></label>

Use the function to<Type> from @nyaf/forms. The parameters are as follows:

  1. An expression to select a property type safe
  2. The property of the element. Any property available in HTMLElement is allowed (and it's restricted to these).
  3. The (optional) type of decorator that's used to pull data from. If it's omitted, the actual data appear.

Obviously you could think about writing this:

<input n-bind={to<ContactModel>(c => c.email, 'value')} />

This is rejected by the compiler, because the property value doesn't exists in HTMLElement. To provide another type, just use a second generic type parameter:

<input n-bind={to<ContactModel, HTMLInputElement>(c => c.email, 'value')} />

Here you tell the compiler, that it's safe to use HTMLInputElement and so the editor allows value as the second parameter. An even smarter way is to use the lambda here, too:

<input n-bind={to<ContactModel, HTMLInputElement>(c => c.email, c => c.value)} />

But, both ways are type safe, even the string is following a constrain. The string is usually shorter, the lambda might use an earlier suggestion from Intellisense.

Even More Smartness

You may also define your component as a generic:

// ContactModel defined elsewhere
export class ContactComponent<T extends ContactModel> extends BaseComponent implements IModel<ContactModel> {

And in that case use a shorter from to express the binding:

<label n-bind={to<T>(c => c.email, c => c.innerText)} />

That's cool, isn't it? Now we have a fully type save binding definition in the middle of the TSX part without any additions to regular HTML.

Recap

Is it worth coding with nyaf forms and vanilla JS? For smaller projects and for apps that must load quickly, yes.

The zipped package of the lib is 2 KBytes. Expanded just 8 KBytes. nyaf itself is required (adding 23 KBytes, 7 KBytes zipped).

However, compared with React or Angular it's a lot simpler. Compared to Vue it's simpler and even smaller, but the delta is not that thrilling.

Restrictions

The package runs, if there are no polyfills, only with ES2015. This limits the usage to any modern browser. It's pretty bold in Electron projects.

Installation

Install the package:

npm i @nyaf/forms -S

Dependencies

Depends on @nyaf/lib.