From 03b70fc9652cf19704ffd79346c9e5b5307083a8 Mon Sep 17 00:00:00 2001 From: Eric BREHAULT Date: Wed, 20 Jan 2021 18:17:51 +0100 Subject: [PATCH] Use GitHub Actions instead of Travis --- .github/workflows/ci.yml | 15 + .github/workflows/deploy.yml | 30 ++ .travis.yml | 50 --- README.md | 586 ++++++++++++++++------------- deploy.sh | 10 - package.json | 2 +- projects/schema-form/karma.conf.js | 28 +- src/karma.conf.js | 30 +- tag-version.sh | 12 + 9 files changed, 405 insertions(+), 358 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deploy.yml delete mode 100644 .travis.yml delete mode 100644 deploy.sh create mode 100755 tag-version.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..63213bf5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,15 @@ +name: CI + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test and build + uses: actions/setup-node@v1 + - run: npm install + - run: npm run test + env: + CI: true diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..d4a66781 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,30 @@ +name: Deploy +env: + CI: true +on: + push: + branches: + - master +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + - name: setup Node + uses: actions/setup-node@v1 + - run: npm install + - name: Github pages + uses: sterlingwes/gh-pages-deploy-action@v1.1 + with: + access-token: ${{ secrets.ACCESS_TOKEN }} + source-directory: dist/ngx-schema-form + build-command: npm run build-demo + - name: Tag + run: ./tag-version.sh + - run: npm run build:lib + - name: Publish + uses: JS-DevTools/npm-publish@v1 + with: + token: ${{ secrets.NPM_TOKEN }} + package: ./dist/schema-form/package.json diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6e0721d2..00000000 --- a/.travis.yml +++ /dev/null @@ -1,50 +0,0 @@ -language: node_js -node_js: - - 12.4.0 -cache: - directories: - - node_modules -dist: trusty -env: - global: - - DBUS_SESSION_BUS_ADDRESS=/dev/null - - DISPLAY=:99.0 - - CHROME_BIN=chromium-browser -before_script: - - sh -e /etc/init.d/xvfb start -install: - - npm install && npm install -g @angular/cli -script: - - npm run test - - npm run build:lib -notifications: - email: - - ebrehault@gmail.com -jobs: - include: - - stage: Tagging - if: (branch = master) AND (tag IS blank) - after_success: source ./deploy.sh - deploy: - provider: releases - api_key: - secure: MYvXFlAVeaWcTuD+MjyFmwJJYXTKFZj8IjY3IhcqvW3EkSJK9BwdRXCGwcmAf5SI3uLXk9xf+t1uScV788frWcqL7jdRZQ04CpjbXuPkyXr+gqScLd9JHHlVTH+v8oAscXlOnyAnzfd+7uOsWLNaTdAzrSxhpnndR9Jw8l33VWl6DKQS3sfocx7YH/nqilOAj0KcF0yXEd1wjIRP7SNojIBFK6A1d/u0mZVRnVX58jeBgDKyl2TinuAJWA4iDBLziPgCvnPVOhTGSfc9m/YC/pAorq/yt74kLFBgKXoK979h927YUHxLTjZNJJhEM3XDRtf4ZJohfZcUs1BwdjYB8gKqV6yLGayQwyzfAkhvX7x93SwcZg/46zwPygzhHWkqX+//874N8RhK1duqYf0TU64K4vc/3fvYdX6cPjvr50u2lQyz3FaQ+QyKNStrk/7WkGkfHaTfFAO/arNEYnxc4/9PPXFy3I8CbFj3SIlc9CZHNZYzWb8NS8QHynAebqla4hcQjdOyi7TFWMqbmIRuynf+B8tCC093MJPjNadbZBdtTpRyO+y5dvD+qA2UBH5NLKHv9PBbi4Po8j9GAohxJPtBLdtGQPvtl64Z0cbAcuHO2o2/PXdtEBgflI/hSwFeK9Zb9GwgMFiBUySvLedoUJDjvKEDWSjvJVA8f92E8ew= - name: Build $PACKAGE_VERSION - on: - repo: guillotinaweb/ngx-schema-form - branch: master - tags: true - - stage: NPM release - if: tag IS present - before_deploy: cd dist/schema-form - deploy: - provider: npm - edge: true - email: ebrehault@gmail.com - skip_cleanup: true - api_key: - secure: Qp/YeV/m9dpJ0vhrEi67qnzyik1CJbJMZr9lqQm9bHAsKTmMj+lQLDRvZ9bwoZy3F3+JHy7m8sdZyfVySr4F8kiI/EVEG2Qw0A89Xsi16L8XxPB8ycBiDnUYPVkwFHnfMG3e+UVW8mLrJkyyWnaSsy4fSjGnArt5bZcGmq4lxRHfWeoO5XU6731FEfMBdiDPQxCUHTFlSe0gRFVyx0wXSQ58RVTDk0OCnRCX0En8L4+Q2JqB6GOAF8N5KtsjN+h15oDoWqLayM2gidkBzET2eLvunPGpT7/6NV4q6Ds0HAglR7ZQLFwp5K4mdTYSBF2QY+FGofQ2hf6VTpWbBL0XCWRVU9W3Ma6dem1UYu3N2XX1K0ogrSRaAD1xy5HLcE5a5yubRRfCdh4fTHlM3OV0/VlfjYuE+QX8f7vT9axKrWdV8vuB68P0RrZb2BoJF124LWrg9h75bWCOxN3aJwxttEobvTqE2s+rCi4cR4Kb30uTPGXpZiY8JTT48YCFsFfulr1FGrzOKbGnCK6wmqrwHRAWHx8DfVbSHkEkzVxPf4mYbCCi+CS3FcAtZqSDYRhMzZvKV7YUf/e78jphBF5rswWR6GBzxTQ13hfg+Ai20tU6er+AJdI1NGvcrv/TZKLw5wWGvF6gKWoZedj7nL1EOoFJx7tVzVTlboXGOWkuQAw= - on: - tags: true - repo: guillotinaweb/ngx-schema-form - branch: master diff --git a/README.md b/README.md index 3b333b7a..533e306d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Ngx Schema Form [![Build Status](https://travis-ci.org/guillotinaweb/ngx-schema-form.svg?branch=master)](https://travis-ci.org/guillotinaweb/ngx-schema-form) +# Ngx Schema Form [![Build Status](https://github.com/guillotinaweb/ngx-schema-form/workflows/CI/badge.svg)](https://github.com/guillotinaweb/ngx-schema-form/actions?query=workflow%3ACI) Ngx Schema Form is an Angular 2+ module allowing you to instanciate an HTML form from a [JSON schema](http://json-schema.org/). @@ -12,19 +12,20 @@ We think `angular-schema-form` is a great Angular 1 library, and when it will mo ## Demo -[Demo](https://guillotinaweb.github.io/ngx-schema-form/dist/ngx-schema-form) +[Demo](https://guillotinaweb.github.io/ngx-schema-form/) ## Features -* Generate a form from a single json schema object -* Generate a form from a default set of html constructs -* Allow initialization from previous values -* Validation handled by z-schema -* Allow injection of custom validators -* Allow declaration of custom widgets -* Allow injection of custom bindings (new!) +- Generate a form from a single json schema object +- Generate a form from a default set of html constructs +- Allow initialization from previous values +- Validation handled by z-schema +- Allow injection of custom validators +- Allow declaration of custom widgets +- Allow injection of custom bindings (new!) ## Installation + To use Ngx Schema Form in your project simply execute the following command: ```bash @@ -34,8 +35,9 @@ npm install ngx-schema-form --save You just have to check that all the peer-dependencies of this module are satisfied in your package.json. ##### JSON Schema + With the installation there comes a JSON-Schema file that declares all specific or additional -properties supported by *ngx-schema-form*. +properties supported by _ngx-schema-form_. When using `*.json` files you may declare it with the `$schema` property to let your IDE's autocompletion help you create a schema-form. @@ -48,8 +50,8 @@ When using `*.json` files you may declare it with the `$schema` property to let ``` - ## Getting started + Here our goal will be to create a simple login form. Let's start by creating a simple AppComponent taking a simple JSON schema as input. @@ -59,51 +61,52 @@ Let's start by creating a simple AppComponent taking a simple JSON schema as inp import { Component } from "@angular/core"; @Component({ - selector:"minimal-app", + selector: "minimal-app", // Bind the "mySchema" member to the schema input of the Form component. - template: '' + template: '', }) - export class AppComponent { // The schema that will be used to generate a form mySchema = { - "properties": { - "email": { - "type": "string", - "description": "email", - "format": "email" + properties: { + email: { + type: "string", + description: "email", + format: "email", }, - "password": { - "type": "string", - "description": "Password" + password: { + type: "string", + description: "Password", + }, + rememberMe: { + type: "boolean", + default: false, + description: "Remember me", }, - "rememberMe": { - "type": "boolean", - "default": false, - "description": "Remember me" - } }, - "required": ["email","password","rememberMe"] - } + required: ["email", "password", "rememberMe"], + }; } ``` Create a module which import the AppComponent and configure Ngx schema form. + ```js //app.module.ts import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; -import { SchemaFormModule, WidgetRegistry, DefaultWidgetRegistry } from "ngx-schema-form"; +import { + SchemaFormModule, + WidgetRegistry, + DefaultWidgetRegistry, +} from "ngx-schema-form"; import { AppComponent } from "./app.component"; @NgModule({ - imports: [ - SchemaFormModule.forRoot(), - BrowserModule - ], + imports: [SchemaFormModule.forRoot(), BrowserModule], declarations: [AppComponent], - providers: [{provide: WidgetRegistry, useClass: DefaultWidgetRegistry}] + providers: [{ provide: WidgetRegistry, useClass: DefaultWidgetRegistry }], }) export class AppModule {} ``` @@ -117,10 +120,10 @@ The input schema support almost all the features listed on the [JSON schema spec ### Accessing the form's value #### Input binding + It is possible to provide initial values to the form. You can set the initial form's value through the `model` input: - ```js @Component({ template: '' @@ -132,11 +135,12 @@ export class AppComponent { ``` #### Output binding + The Form component provides the `onChange` output binding of which value represents the value of the form. For instance, you can display the current forms's value with the following template: ```js -template: '{{value | json}}' +template: '{{value | json}}'; ``` The `model` property allow two-way data binding: @@ -146,29 +150,30 @@ The `model` property allow two-way data binding: ``` ### Widgets + Each field can be displayed using a specific widget. To declare the widget you want to use, add its `id` to the field's definition: ```js mySchema = { - "properties": { - "email": { - "type": "string", - "description": "email", - "format": "email" + properties: { + email: { + type: "string", + description: "email", + format: "email", }, - "password": { - "type": "string", - "description": "Password", - "widget": "password"// == "widget": {"id": "password"} + password: { + type: "string", + description: "Password", + widget: "password", // == "widget": {"id": "password"} }, - "rememberMe": { - "type": "boolean", - "default": false, - "description": "Remember me" - } - } -} + rememberMe: { + type: "boolean", + default: false, + description: "Remember me", + }, + }, +}; ``` If there is no widget declared in a given property's schema, its type is used as widget id and the [default registry](#default-widgets-registry) gives a default widget (see details below). @@ -177,47 +182,48 @@ The following JSON schema is equivalent with the above example: ```js mySchema = { - "properties": { - "email": { - "type": "string", - "description": "email", - "format": "email", - "widget": "string" + properties: { + email: { + type: "string", + description: "email", + format: "email", + widget: "string", }, - "password": { - "type": "string", - "description": "Password", - "widget": "password"// == "widget": {"id": "password"} + password: { + type: "string", + description: "Password", + widget: "password", // == "widget": {"id": "password"} }, - "rememberMe": { - "type": "boolean", - "default": false, - "description": "Remember me", - "widget": "boolean" - } - } -} + rememberMe: { + type: "boolean", + default: false, + description: "Remember me", + widget: "boolean", + }, + }, +}; ``` Some widgets accept parameters as input, in such cases, it is possible to provide them in the schema directly within the `widget` property (here the [TinyMCE widget](https://github.com/fbessou/ng2sf-tinymce) ): ```js mySchema = { - "properties": { - "pageContent": { - "type": "string", - "description": "Page content", - "widget": { - "id": "richtext", - "plugins": "textcolor colorpicker", - "toolbar": "forecolor backcolor" - } - } - } -} + properties: { + pageContent: { + type: "string", + description: "Page content", + widget: { + id: "richtext", + plugins: "textcolor colorpicker", + toolbar: "forecolor backcolor", + }, + }, + }, +}; ``` ### Default widget's registry + Available widgets are managed through a `WidgetRegistry`. The default registry ([`DefaultWidgetRegistry`](./projects/schema-form/src/lib/defaultwidgets/defaultwidgetregistry.ts)) contains many widgets listed below, ordered by type: @@ -262,6 +268,7 @@ Note that the select and radio widgets rely on the `oneOf` property: ``` ### Actions and buttons + Each schema can be extended by adding buttons after its widget. ```js @@ -269,65 +276,73 @@ Each schema can be extended by adding buttons after its widget. @Component({ selector: "minimal-app", // Bind the actions map to the the "actions" input - template: '' + template: '', }) export class AppComponent { // The schema that will be used to generate a form mySchema = { - "properties": { - "email": { - "type": "string", - "description": "email", - "format": "email" + properties: { + email: { + type: "string", + description: "email", + format: "email", }, - "password": { - "type": "string", - "description": "Password", - "buttons": [{ - "id": "reset", - "label": "Reset" - }] + password: { + type: "string", + description: "Password", + buttons: [ + { + id: "reset", + label: "Reset", + }, + ], + }, + rememberMe: { + type: "boolean", + default: false, + description: "Remember me", }, - "rememberMe": { - "type": "boolean", - "default": false, - "description": "Remember me" - } }, - "required": ["email", "password", "rememberMe"], - "buttons": [{ - "id": "alert", // the id of the action callback - "label": "Alert !" // the text inside the button - }] - } + required: ["email", "password", "rememberMe"], + buttons: [ + { + id: "alert", // the id of the action callback + label: "Alert !", // the text inside the button + }, + ], + }; // Declare a mapping between action ids and their event listener myActions = { - "alert": (property) => { alert(JSON.stringify(property.value)) }, - "reset": (property) => { property.reset() } - } + alert: (property) => { + alert(JSON.stringify(property.value)); + }, + reset: (property) => { + property.reset(); + }, + }; } ``` #### Render buttons -You may define you own widget to create buttons by +You may define you own widget to create buttons by overriding the default widget for action buttons or create completely customized button widgets. ##### Override -Override the default action button widget +Override the default action button widget in your `WidgetRegistry` implementation and register your own button widget. ```js - this.register('button', MyButtonWidgetComponent); +this.register("button", MyButtonWidgetComponent); ``` ##### Custom -Define a custom button widget by +Define a custom button widget by setting the property `button.widget` in the schema ```json @@ -346,35 +361,33 @@ setting the property `button.widget` in the schema } ] } -``` +``` and then register it in your `WidgetRegistry` implementation ```js - this.register('my_custom_button', MyCustomButtonWidgetComponent); +this.register("my_custom_button", MyCustomButtonWidgetComponent); ``` - + ##### Binding The button widget will get provided the `button` object form the schema -including the `button.action` from the action registry +including the `button.action` from the action registry and the `formProperty` object. -To be fully AOT compatible -the custom button widget may then extend `ButtonWidget` or +To be fully AOT compatible +the custom button widget may then extend `ButtonWidget` or provide the properties `button` and `formProperty` by it self. ```js - import {Component} from "@angular/core"; - import {ButtonWidget} from 'ngx-schema-form/dist/defaultwidgets' - - @Component({ - selector: 'sf-button-widget', - templateUrl: 'custom-button.widget.html' - }) - export class CustomWidgetComponent extends ButtonWidget { - - } +import { Component } from "@angular/core"; +import { ButtonWidget } from "ngx-schema-form/dist/defaultwidgets"; + +@Component({ + selector: "sf-button-widget", + templateUrl: "custom-button.widget.html", +}) +export class CustomWidgetComponent extends ButtonWidget {} ``` ```js @@ -389,6 +402,7 @@ provide the properties `button` and `formProperty` by it self. ``` ### Advanced validation + JSON schema provides validation against a static schema but its often necessary to provide other validation rules. The Form component accepts a `validators` input bound to a map between a field id and a validation function. The validation function takes three arguments: the value of the field, the property corresponding to it and the form object. @@ -401,38 +415,43 @@ To perform this check we create a custom validator: @Component({ selector: "minimal-app", // Bind the validator map to the the "validators" input - template: '' + template: + '', }) export class AppComponent { mySchema = { - "properties": { - "email": { - "type": "string", - "description": "email", - "format": "email" + properties: { + email: { + type: "string", + description: "email", + format: "email", }, - "password": { - "type": "string", - "description": "Password" + password: { + type: "string", + description: "Password", + }, + passwordCheck: { + type: "string", + description: "Password (verification)", }, - "passwordCheck": { - "type": "string", - "description": "Password (verification)" - } }, - "required": ["email", "password", "passwordCheck"] - } + required: ["email", "password", "passwordCheck"], + }; // Declare a mapping between action ids and their implementations myValidators = { "/passwordCheck": (value, property, form) => { - const passwordProperty = formProperty.findRoot().getProperty('password') - if (passwordProperty.value !== undefined && property.valid && value !== passwordProperty.value) { - return { "passwordCheck": { "expectedValue": "same as 'password'" } } + const passwordProperty = formProperty.findRoot().getProperty("password"); + if ( + passwordProperty.value !== undefined && + property.valid && + value !== passwordProperty.value + ) { + return { passwordCheck: { expectedValue: "same as 'password'" } }; } return null; - } - } + }, + }; } ``` @@ -449,144 +468,153 @@ When you type in the name of the parent (first person) the name of the children @Component({ selector: "minimal-app", // Bind the bindings map to the the "bindings" input - template: '' + template: + '', }) export class AppComponent { - mySchema = - { - "type": "object", - "title": "Example with custom bindings.", - "description": "Type a family name to see how the name gets synchronized with the children.", - "properties": { - "name": { - "type": "string", - "title": "Surname" - }, - "forename": { - "type": "string", - "title": "Forename" - }, - "children": { - "type": "array", - "title": "Family", - "items": { - "type": "object", - "title": "Children", - "properties": { - "name": { - "type": "string", - "title": "Surname" - }, - "forename": { - "type": "string", - "title": "forename" - }, - "age": { - "type": "number", - "title": "age" - } - } - } - } - } - } + mySchema = { + type: "object", + title: "Example with custom bindings.", + description: + "Type a family name to see how the name gets synchronized with the children.", + properties: { + name: { + type: "string", + title: "Surname", + }, + forename: { + type: "string", + title: "Forename", + }, + children: { + type: "array", + title: "Family", + items: { + type: "object", + title: "Children", + properties: { + name: { + type: "string", + title: "Surname", + }, + forename: { + type: "string", + title: "forename", + }, + age: { + type: "number", + title: "age", + }, + }, + }, + }, + }, + }; // Declare a mapping between field and event-id myFieldBindings = { - '/name': [ - { - 'input': (event, formProperty: FormProperty) => { - const parent: PropertyGroup = formProperty.findRoot(); - - /** - * Set the input value for the children - */ - const child1: FormProperty = parent.getProperty('children/0/name'); - - child1.setValue(formProperty.value, false); - - const child2: FormProperty = parent.getProperty('children/1/name'); - child2.setValue(event.target.value, false); - - /** - * Get the input value for all the children - */ - for (const objectProperty of parent.getProperty('children').properties) { - console.log('Value for child ', objectProperty, objectProperty.properties['name'].value); - } + "/name": [ + { + input: (event, formProperty: FormProperty) => { + const parent: PropertyGroup = formProperty.findRoot(); + + /** + * Set the input value for the children + */ + const child1: FormProperty = parent.getProperty("children/0/name"); + + child1.setValue(formProperty.value, false); + + const child2: FormProperty = parent.getProperty("children/1/name"); + child2.setValue(event.target.value, false); + + /** + * Get the input value for all the children + */ + for (const objectProperty of parent.getProperty("children") + .properties) { + console.log( + "Value for child ", + objectProperty, + objectProperty.properties["name"].value + ); } - } - ] - }; + }, + }, + ], + }; } ``` ### Conditional fields + It is possible to make the presence of a field depends on another field's value. -To achieve this you just have to add a `visibleIf` property to a field's definition. +To achieve this you just have to add a `visibleIf` property to a field's definition. **Value** The value to match is set as array item. Setting multiple items will make the visiblity condition `true` if one of the values matches. -If it is required to match all values head over to the section `visibleIf` with `allOf` condition. +If it is required to match all values head over to the section `visibleIf` with `allOf` condition. **$ANY$** -Adding the value `$ANY$` to the array of conditional values, will make the field visible for any value inserted. +Adding the value `$ANY$` to the array of conditional values, will make the field visible for any value inserted. ```js @Component({ selector: "minimal-app", - template: '' + template: '', }) export class AppComponent { mySchema = { - "properties": { - "name": { - "type": "string", - "description": "Username" + properties: { + name: { + type: "string", + description: "Username", }, - "comment": { - "type": "string", - "description": "Comment" + comment: { + type: "string", + description: "Comment", }, - "registerNewsletter": { - "type": "boolean", - "description": "I want to receive the newsletter", - "default": false, - "visibleIf": { - "comment": ['$ANY$'] - } + registerNewsletter: { + type: "boolean", + description: "I want to receive the newsletter", + default: false, + visibleIf: { + comment: ["$ANY$"], + }, }, - "registerEmail": { - "type": "string", - "description": "Email", - "format": "email", + registerEmail: { + type: "string", + description: "Email", + format: "email", // Declare that this field must be displayed only if registerNewsletter is true - "visibleIf": { - "registerNewsletter": [true] - } - } + visibleIf: { + registerNewsletter: [true], + }, + }, }, - "required": ["name", "comment", "registerToNewsletter"] - } + required: ["name", "comment", "registerToNewsletter"], + }; } ``` + **$EMPTY$** Assigning an empty Object to 'visibleIf' is interpreted as _visibleIf_ nothing, thereby the widget is hidden and not present in model. ```js mySchema = { - "properties": { - "hidden": { - "type": "boolean", - "visibleIf": { } - } - } - } + properties: { + hidden: { + type: "boolean", + visibleIf: {}, + }, + }, +}; ``` `visibleIf` may also declare conditional binding by using `oneOf` or `allOf` properties. Where `oneOf` is handled as `OR` and `allOf` is handled as `AND`. + ``` "visibleIf": { "allOf": [ @@ -603,17 +631,19 @@ Where `oneOf` is handled as `OR` and `allOf` is handled as `AND`. ] } ``` -The `oneOf` a is prioritized before the `allOf` and both are prioritized before the + +The `oneOf` a is prioritized before the `allOf` and both are prioritized before the property binding. - + _`oneOf` and `allOf` oneOf and allOf are reserved keywords and not suitable as property names_ **Arrays** -To address array items or not yet existing properties the `visibleIf` +To address array items or not yet existing properties the `visibleIf` condition path may contain wildcard `*`. -e.g +e.g + ``` "visibleIf": { "oneOf": [ @@ -626,10 +656,11 @@ e.g } ``` -To address a specific item the `visibleIf` +To address a specific item the `visibleIf` condition path should contain the index position. -e.g +e.g + ``` "visibleIf": { "oneOf": [ @@ -667,13 +698,14 @@ and `target` is the `FormProperty` that has been defined by the `path`. } ``` - #### Hidden fields + When a field has been made invisible by the condition `visibleIf` then the property of the invisible field will be missing in the result model. If there is need to submit default values that are not visible for the form the `widget.id` `hidden` might be the better choice + ```js mySchema = { "properties": { @@ -689,6 +721,7 @@ the `widget.id` `hidden` might be the better choice } } ``` + so the value of the hidden field will be bound to the output model ```js @@ -700,10 +733,12 @@ so the value of the hidden field will be bound to the output model ``` ### Fields presentation and ordering + As a JSON object is an unordered collection you can't be sure your fields will be correctly ordered when the form is built. The `order` and `fieldsets` entries of the schema are here to organize your fields. #### Ordering + The `order` entry is an array listing all the fields ids in the order they must appear in the form: ```js @@ -718,6 +753,7 @@ The `order` entry is an array listing all the fields ids in the order they must ``` #### Fieldsets + With the `fieldsets` property, you can describe the different parts of the form and the fields they contain: ```js @@ -820,37 +856,46 @@ export class MyComponent { ``` ## Creating a custom widget + Ngx schema form allows you to create your own widget. Note: Currently this feature is not completely defined and the API might change. You need to derivate the widget you want to customize: + ```javascript @Component({ - selector: 'mdl-sf-string-widget', - templateUrl: './string.widget.html' + selector: "mdl-sf-string-widget", + templateUrl: "./string.widget.html", }) export class MyStringWidget extends StringWidget {} ``` You need to provide its html template (let's imagine we want to use the Material Design text field): + ```html - + ``` And you need to declare it in a custom registry: + ```javascript -import { MyStringWidget } from './mystring'; +import { MyStringWidget } from "./mystring"; export class MyWidgetRegistry extends DefaultWidgetRegistry { constructor() { super(); - this.register('string', MyStringWidget); + this.register("string", MyStringWidget); } } ``` @@ -868,6 +913,7 @@ providers: [{provide: WidgetRegistry, useClass: MyWidgetRegistry}], ``` Note: you will also need to import `ReactiveFormsModule` if you want to be able to use form control: + ```javascript import { ReactiveFormsModule } from '@angular/forms'; ... @@ -881,20 +927,23 @@ import { ReactiveFormsModule } from '@angular/forms'; ``` ## Create form from html instead of json schema + Ngx schema form allows you to create forms from angular html templates too. -For this you only need to import `TemplateSchemaModule` to your app, and use the +For this you only need to import `TemplateSchemaModule` to your app, and use the directive `templateSchema` on sf-form. The followin html will generate the same form as the json schema in getting started section. ```html - + Email - Password + + Password + Remember Me - ``` + For more details see example app. ## Development and build @@ -921,6 +970,7 @@ npm start ``` ## Building the API documentation + You can build an HTML version of the API documentation by running the following command: ```bash diff --git a/deploy.sh b/deploy.sh deleted file mode 100644 index b56b302d..00000000 --- a/deploy.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -git config --global user.email "build@travis-ci.com"; -git config --global user.name "Travis CI"; -export PACKAGE_VERSION=$(npm run get_version | tail -n 1); -if ! [ $(git tag -l "$PACKAGE_VERSION") ]; then - echo "TAGGING $PACKAGE_VERSION..."; - export TRAVIS_TAG=$PACKAGE_VERSION; - git tag $PACKAGE_VERSION; -fi diff --git a/package.json b/package.json index 9ca566c2..b56d8cda 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "ng build", "copy:schema": "node -e \"var src='./schema/ngx-schema-form-schema.json'; var dest='./dist/schema-form/ngx-schema-form-schema.json'; var fs = require('fs'); if (fs.existsSync(src)) { var data = fs.readFileSync(src, 'utf-8'); fs.writeFileSync(dest, data);}\"", "build:lib": "ng build --prod schema-form && npm run copy:schema && cp ./README.md ./dist/schema-form/", - "build-demo": "ng build --prod --base-href /ngx-schema-form/dist/ngx-schema-form/", + "build-demo": "ng build --prod --base-href /ngx-schema-form/", "test:lib": "ng test schema-form --watch=false", "test": "ng test --watch=false", "lint": "ng lint", diff --git a/projects/schema-form/karma.conf.js b/projects/schema-form/karma.conf.js index 4c5f8d03..8318fa0f 100644 --- a/projects/schema-form/karma.conf.js +++ b/projects/schema-form/karma.conf.js @@ -3,29 +3,29 @@ module.exports = function (config) { config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], + basePath: "", + frameworks: ["jasmine", "@angular-devkit/build-angular"], plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') + require("karma-jasmine"), + require("karma-chrome-launcher"), + require("karma-jasmine-html-reporter"), + require("karma-coverage-istanbul-reporter"), + require("@angular-devkit/build-angular/plugins/karma"), ], client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser + clearContext: false, // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { - dir: require('path').join(__dirname, '../../coverage'), - reports: ['html', 'lcovonly'], - fixWebpackSourcePaths: true + dir: require("path").join(__dirname, "../../coverage"), + reports: ["html", "lcovonly"], + fixWebpackSourcePaths: true, }, - reporters: ['progress', 'kjhtml'], + reporters: ["progress", "kjhtml"], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, - browsers: ['Chrome'], - singleRun: false + browsers: ["ChromeHeadless"], + singleRun: false, }); }; diff --git a/src/karma.conf.js b/src/karma.conf.js index b6e00421..40cd5cb1 100644 --- a/src/karma.conf.js +++ b/src/karma.conf.js @@ -3,29 +3,29 @@ module.exports = function (config) { config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], + basePath: "", + frameworks: ["jasmine", "@angular-devkit/build-angular"], plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') + require("karma-jasmine"), + require("karma-chrome-launcher"), + require("karma-jasmine-html-reporter"), + require("karma-coverage-istanbul-reporter"), + require("@angular-devkit/build-angular/plugins/karma"), ], client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser + clearContext: false, // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { - dir: require('path').join(__dirname, '../coverage'), - reports: ['html', 'lcovonly'], - fixWebpackSourcePaths: true + dir: require("path").join(__dirname, "../coverage"), + reports: ["html", "lcovonly"], + fixWebpackSourcePaths: true, }, - reporters: ['progress', 'kjhtml'], + reporters: ["progress", "kjhtml"], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, - browsers: ['Chrome'], - singleRun: false + browsers: ["ChromeHeadless"], + singleRun: false, }); -}; \ No newline at end of file +}; diff --git a/tag-version.sh b/tag-version.sh new file mode 100755 index 00000000..2949a05e --- /dev/null +++ b/tag-version.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +git config --global user.email "github@github.com"; +git config --global user.name "GitHub Actions"; +export PACKAGE_VERSION=$(npm run get_version | tail -n 1); +echo "PACKAGE VERSION: $PACKAGE_VERSION" +echo "Search in existing… $(git tag -l "$PACKAGE_VERSION")" +if ! [ $(git tag -l "$PACKAGE_VERSION") ]; then + echo "TAGGING $PACKAGE_VERSION..."; + git tag $PACKAGE_VERSION; + git push --tags; +fi \ No newline at end of file