Extension:WikiLambda/Frontend Architecture

From Linux Web Expert

Revision as of 16:51, 25 January 2024 by imported>GChoi-WMF ('linting' for most bulleted lists)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

The Frontend layer of the WikiLambda extension consists on a Vue app that allows easy visualization and manipulation of ZObjects in the most convenient available human language.

This app is written for Vue.js, using Vuex and Codex component library, and is fully available in the WikiLambda resource module, ext.wikilambda.edit

The basic functions:

  • Load the ZObject requested in the URL for viewing or editing purposes.
  • Translate all ZObject ids (Zn) and ZKey ids (ZnKm) into a human readable string in the user preferred language or its closest available fallback language.
  • Render different parts of the ZObject with template components that allow users to view or edit them more intuitively.
  • Provide additional widgets that enrich the viewing and editing experience.

Entry point and Views

The Vue entry point is the base component, App.vue. Given the values passed by the config variable wgWikiLambda and in the URL parameters, this component:

  1. Requests for the necessary data
  2. Initializes the internal state
  3. Loads the appropriate view

(Details on initializing the page data are in Page initialization section below.)

The views define the structure of each page, applying and placing widgets and content section. The components are stored in the views directory and are the following:

Function Editor

The FunctionEditor.vue view loads when we create a new ZObject of type Function/Z8 or edit an existing one. The App entry point renders Function Editor when we:

The Function Editor content consists on a simple form page having no additional widgets.

Function Viewer

The FunctionViewer.vue view loads when we view an existing Function/Z8 object. The App entrypoint renders Function Viewer when we:

The Function Viewer page accommodates the widgets: About, Function Evaluator, Implementations Table and Tests Table.

Default View

The DefaultView.vue view is loaded when creating, editing, or viewing all objects that are not a Function/Z8. The App entry point renders Default View when we:

The page is formed by a central Content section and the About widget. And specifically, when viewing or editing objects of type Implementation/Z14 or Test/Z20, it also adds the Function Explorer, Function Evaluator, and Function Report widgets.

Function Evaluator

The FunctionEvaluator.vue view is loaded when opening the Special:RunFunction page. E.g. https://wikifunctions.beta.wmflabs.org/wiki/Special:RunFunction

Should we add information about wgWikiLambda in Manual:Interface/JavaScript#mw.config?

Data Flows

Persistent ZObject Initialization

Every Create, Edit or View page represents an object of type Persistent Object/Z2. When we create a new ZObject, the initialization process creates a blank Persistent Object/Z2, and when we edit or view an existing one, the initialization flow requests it via the MediaWiki API and sets it as the page root ZObject.

As persisted, ZObjects are valid JSON objects and they are stored in their canonical (or short) form. However, while initializing the Frontend app, objects undergo two transformations:

  1. Terminal nodes (Strings/Z6 and References/Z9) are transformed into their normal (or long) form. Lists are left as arrays, in their canonical form (aka. Benjamin Arrays).
  2. Then, the resulting JSON is transformed into a flattened table form, so that each of their nodes has a unique id by which the Vue components can easily access and modify their values. (Details in the section below).

ZObject Table

The ZObject represented on the page is stored in the zobject.js store module as a flat table, where every node (and hence, every child ZObject represented under that node) can be reached directly by any component using its unique row id, without having to navigate the tree of the original JSON ZObject.

The table has the following columns:

  • id: a temporary, non-persisted, unique and numerical id that identifies the row
  • parent: the numerical id of the parent row; empty if is the root node of a ZObject
  • key: the key of the key-value pair of the ZObject node; empty if is the root node of a ZObject
  • value: contains the string value if the key-value is terminal; else contains OBJECT/ARRAY depending on what type of value this key contains

For example, if we had to transform the following ZObject JSON representation:

{
  "Z1K1": "Z6",
  "Z6K1": "terminal string"
}

We would first create the root node to indicate that it's an object:

id parent key value
0 - - OBJECT

And then go into every keys of the given object and create one row for each, where it's parent is the node created earlier:

id parent key value
0 - - OBJECT
1 0 "Z1K1" "Z6"
2 0 "Z6K1" "terminal string"

When the object to represent is a list, each of their items will have the item index in the key column. For example, to represent a more complex object like a the following Multilingual text/Z12 (in canonical form, for the sake of a simpler example):

{
  "Z1K1": "Z12",
  "Z12K1": [
    "Z11",
    {
      "Z1K1": "Z11",
      "Z11K1": "Z1002",
      "Z11K2": "monolingual text"
    }
  ]
}

The representation of this object as a flattened table would be:

id parent key value
0 - - OBJECT
1 0 "Z1K1" "Z12"
2 0 "Z12K1" ARRAY
3 2 "0" "Z11"
4 2 "1" OBJECT
5 4 "Z1K1" "Z11"
6 4 "Z11K1" "Z1002"
7 4 "Z11K2" "monolingual text"

The row with id 2 and key Z12K1 contains a value of type ARRAY, and is the parent of the two items of the array:

  • Row 3 is the first item, with the terminal value "Z11"
  • Row 4 is the second item, with non-terminal value of type OBJECT, which has three child nodes, rows 5, 6, and 7.

As mentioned, this previous example is, for simplicity, shown fully in canonical form. However, the real way that we would represent these would be after normalizing the terminal nodes (Strings/Z6 and References/Z9). Let's look at a Monolingual string/Z11 object to see a more real representation of these transformations.

First, as returned by the MediaWiki API, we would have the following object:

{
  "Z1K1": "Z11",
  "Z11K1": "Z1002",
  "Z11K2": "monolingual text"
}

After normalizing its terminal nodes, we would have the following, more extensive:

{
  "Z1K1": {
    "Z1K1": "Z9",
    "Z9K1": "Z11"
  },
  "Z11K1": {
    "Z1K1": "Z9",
    "Z9K1": "Z1002"
  },
  "Z11K2": {
    "Z1K1": "Z6",
    "Z6K1": "monolingual text"
  }
}

And finally, its table representation would end up being like this:

id parent key value
0 - - OBJECT
1 0 "Z1K1" OBJECT
2 1 "Z1K1" "Z9"
3 1 "Z9K1" "Z11"
4 0 "Z11K1" OBJECT
5 4 "Z1K1" "Z9"
6 4 "Z9K1" "Z1002"
7 0 "Z11K2" OBJECT
8 7 "Z1K1" "Z6"
9 7 "Z6K1" "monolingual text"

This structure allows the template to create components for each node depending on their particular type, and easily access or modify their data just by using the row id. For example, the Monolingual text/Z11 object detailed above would be rendered as shown below, after translating every Zid and ZKey id into human readable language:

Read page Edit source page
File:Monolingual view example.png File:Monolingual edit example.png

The terminal objects of type Reference/Z9 are rendered as links in the Read page, and as a Lookup selector in the Edit page, and the terminal object of type String/Z6 is rendered as plain text in the Read page, and as a text input in the Edit page.

Thanks to the flattened ZObject table representation, when changing the value of the text "monolingual text" in the input field, the component will simply change the content of the column "value" of the row id 9. The complexity of the operation will be the same no matter how deep in the ZObject JSON the node is.

Detached ZObjects

On occasion, it is necessary to use the Default component tree (initiated by an instance of the ZObjectKeyValue.vue component) to represent additional ZObjects in the page. For example, when seeing the Function Evaluator widget in a Test/Z20 page (E.g. https://www.wikifunctions.org/view/en/Z8021), the Default component is representing the following independent ZObjects:

  • The main ZObject (in this case, the Test/Z0) represented by the page.
  • The Function Call/Z7 object built by the widget for it to be called, always editable.
  • The Evaluation result/Z22 object returned after the call, not editable.

As explained above, the components need the ZObjects to be represented and saved in the state as a ZObject flat table structures. The main ZObject will always be initialized first, so the root row ID will always be 0. When we need additional ZObjects saved in the table, we create new nodes parting from the next available row ID. Whenever a node has its key and parent properties set to undefined, it is the root of a detached ZObject.

id parent key value
main ZObject root 0 - - OBJECT
1 0 "Z1K1" "Z6"
2 0 "Z6K1" "terminal string"
first detached ZObject root 3 - - OBJECT
4 3 "Z1K1" "Z6"
5 3 "Z6K1" "detached object"

After making changes to the main ZObject, the detached ZObjects will remain embedded in the ZObject table, and they can be returned as long as their root Row ID is available.

id parent key value
main ZObject root 0 - - OBJECT
1 0 "Z1K1" "Z6"
2 0 "Z6K1" OBJECT
first detached ZObject root 3 - - OBJECT
4 3 "Z1K1" "Z6"
5 3 "Z6K1" "detached object"
continuation of main ZObject 6 2 "Z1K1" "Z9"
7 2 "Z9K1" "Z10000"

Label Data Initialization

Once the main ZObject is requested, transformed and stored as as described in the section above, we have a flat, accessible, easily editable representation of all its nodes. However, the object is still represented in its non-readable form (with language-independent Zids and ZKey ids instead of human-readable labels).

The final step of initialization is to make sure that all the ZObject and ZKey ids are shown in the user language, if available, or in their closest available fallback language:

Before label data For English For Spanish
File:Before label data initialization.png File:With label data in English.png File:With label data in Spanish.png

To accomplish this, the global store keeps a glossary of labels and their translations to the user language, stored in the module library.js, and all of the components request the translations to this module.

Details on how to use the library module are below in the Library module section.

Page Submission

Before the final submission, the main ZObject undergoes a series of validations and, if those are successful, a series of transformations. All of these are placed in the submissions module.

The component that handles, triggers and orchestrates all the submission-related actions is the Publish widget detailed below.

Validations

When creating a new ZObject or editing an existing one, the "Publish" button is enabled. Clicking this publish button triggers the validateZObject action. This action performs a series of superficial validations for different ZObject types, which must pass before the ZObject is submitted. These validations are the following:

  • For a Function/Z8 page:
    • The output type must be set,
    • For every input, if any, its type must be set.
  • For an Implementation/Z14 page:
    • The implementation function field must be set,
    • If the implementation is a composition, the composition function call must be defined,
    • If the implementation is code, the programming language must be defined and the code string must contain something.
  • For a Test/Z20 page:
    • The test function field must be set,
    • The test function call must be configured,
    • The test validation call must be also configured.

Failure to pass these validations will render error messages in the fields that are missing.

If the validation passes, the publishing process will proceed by showing the Publish dialog.

Transformations

After adding an edit message and clicking the final "Publish" in the dialog, the submitZObject action in the submission module is triggered, which first does the transformations listed in the transformZObjectForSubmission action:

  1. Removes empty monolingual values from the Persistent object name/Z2K3: An empty monolingual value that has either unset Language/Z11K1 or Text/Z11K2 fields.
  2. Removes empty monolingual values from the Persistent object description/Z2K5
  3. Removes empty alias sets from the Persistent object alias/Z2K4: An empty alias set has either an empty language value, or an empty list of strings, after removing the empty strings from the list. (E.g. [ "Z6", "" ] and [ "Z6" ] are both considered empty alias lists)
  4. For Function/Z8, removes empty arguments: An empty argument is one that has no label and no type.
  5. Remove Typed list items marked for deletion: Items are marked for deletion when a Typed list that contains items has its type changed, so their items don't match the type anymore.
  6. For Function/Z8, disconnect all its implementations and tests if the Function signature has changed: The function signature changes when either the inputs (number of inputs or their types) or the output type has changed.

Once all these transformations have successfully gone through, the ZObject is then fully canonicalized and submitted for creation or edition to the wikilambda_edit API.

Vuex Global State

The WikiLambda Vue app uses Vuex for storing, accessing, and handling the global state.

The basic functions:

  • Store the main ZObject for the given page. The object is transformed into a flat table structure before being stored as described in the ZObject table section.
  • Store the necessary auxiliary data to enrich the experience in the UI. For example, the human-readable translations of all the Zids and ZKey ids.
  • Provide the necessary actions to modify safely the main ZObject.

The state is divided into modules that handle different features, but the most important modules are: zobject, zFunction and library, which are described below along with their most important actions and getters.

Notes on Getters

Vuex getters work as computed properties. After requesting the getter in the component using mapGetters like this:

  computed: $.extend(                  
    mapGetters( [         
      'getCurrentZObjectId',
      'getZObjectTypeByRowId'
    ] ), {
    ...
  } ),

The getter getCurrentZObjectId is already computed and can be used anywhere in the component without having to invoke it as a function.

zobjectId: function() {
  return this.getCurrentZObjectId;
}

However, some getters need an input. In these cases, getters might return a function, that needs to be invoked from the component. This is the case of getters like getZObjectTypeByRowId, which needs the rowId passed as an input:

zobjectType: function() {
  return this.getZObjectTypeByRowId( this.rowId );
}

This needs to be taken into consideration when mocking the Vuex getters in the jest unit tests, and every getter needs to be mocked to return either a value or a function. For this reason, the testing infrastructure provides two different helpers to mock getters (see in getterHelpers.js):

  • createGettersWithFunctionsMock: mocks a getter that returns a function and then is invoked to return the given value.
  • createGetterMock: mocks a simple getter that returns the given value.

For an example of this, see the test for the ZReference.vue component:

const createGettersWithFunctionsMock = require( '../../helpers/getterHelpers.js' ).createGettersWithFunctionsMock,
	createGetterMock = require( '../../helpers/getterHelpers.js' ).createGetterMock;

describe( 'ZReference', () => {
	var getters;
	beforeEach( () => {
		getters = {
			getUserLangCode: createGetterMock( 'en' ),
			getLabel: createGettersWithFunctionsMock( 'String' ),
			getZReferenceTerminalValue: createGettersWithFunctionsMock( 'Z6' ),
			getZObjectKeyByRowId: createGettersWithFunctionsMock( 'Z1K1' )
		};
		global.store.hotUpdate( {
			getters: getters
		} );
	} );
    ...
 } );


Library module

The library module requests, transforms and stores all the auxiliary information that the UI needs from other ZObjects that are not the main one. The library module stores two main collections:

  • l abels: Glossary of all ZObject and ZKey ids in their abstract form and their their most relevant available label. Each value contains the human-readable label, the language of the label, and the Zid, and the objects are indexed by Zid.
{
  "Z1": { "label": "Object", "lang": "Z1002", "zid": "Z1" },
  "Z1K1": { "label": "type", "lang": "Z1002", "zid": "Z1K1" },
  "Z1002": { "label": "Inglés", "lang": "Z1003", "zid": "Z1002" },
  "Z1003": { "label": "Spanish", "lang": "Z1002", "zid": "Z1003" },
}
  • objects: Cached ZObjects that were requested by the library module and might be needed in the future.
Actions

Whenever the UI needs additional information from non-root objects, it must trigger the action fetchZids (see more details in the inline documentation), which will:

  • Check if the requested object has been fetched and hence is already cached.
  • If not, request the object to the wikilambdaload_zobjects API with the parameters language set to the user language, and get_dependencies set to true.
  • For each object in the response, do:
    • Save the object in the cached objects collection
    • Save its label in the labels collection
    • If it's a Function: save its argument labels in the labels collection
    • If it's a Type: save its key labels in the labels collection
    • Perform the action fetchZids again with all the returned languages that were not observed before.
Getters

The library store has a number of useful getters which return functions and allow us to reactively look into the objects stored in the module. The most important ones are:

  • getLabelData: Returns the LabelData object saved for a given Zid or ZKey id. This is necessary when we want to render the label of a particular key or value and we want to put the language chip whenever it's not in our language. LabelData is, for example, the object type passed into components like the LocalizedLabel.vue component.
  • getLabel: Returns the label for a given Zid or ZKey, if present, or else returns the requested Zid or ZKey string. This means that we can have reactive labels in the UI. For example, the template <label>{{ getLabel( 'Z1K1' ) }}</label>, will produced the element <label>Z1K1</label> if the library store doesn't have the label for 'Z1K1'. Once the library store module has fetched and stored the object Z1 the element will be instantly replaced by <label>type</label>.

ZObject module

The ZObject module stores the main ZObject of the page and provides a collection of getters and actions to navigate the table nodes, return or change its values.

All of the methods in this module operate over the ZObject table structure described above, so they all require the row IDs to find or edit the required values. For this reason, all of the getters in this module will return functions that need to be invoked with the required row ID.

Actions

There is a range of actions in this module from more nuclear operations, which are normally used internally by other methods of this module, and other which are generally accessed by components which perform more complex actions. The most commonly used methods are:

setValueByRowIdAndPath

Given a row ID and a path of keys, sets the final node to the given value. For example, assuming that starting from row ID=1 we have the following Monolingual String object (in normal form):

{
  "Z1K1": { "Z1K1": "Z9", "Z9K1": "Z11" },
  "Z11K1": { "Z1K1": "Z9", "Z9K1": "Z1002" },
  "Z11K2": { "Z1K1": "Z6", "Z6K1": "old string" }
}

We can use this action to change the value of the string by invoking it with the starting row ID and the path of keys that we should follow to reach the end value:

setValueByRowIdAndPath( 1, [ "Z11K2", "Z6K1" ], "new string" );

Which will first navigate the ZObject table, starting at row ID 1, down the array of keys specified in the path, and will set the value "old string" to the given value "new string".

changeType

This is another of the widely used actions in the UI, and it's code lives in the addZObject submodule.

Given a row Id and a new type, initializes the value under that row Id to a new object of the given type. For example, if we look again at the previous Monolingual String object, and we assume that the row ID that contains the key "Z11K1" is the row ID 5 (in canonical form, for simplicity):

{
  "Z1K1": "Z11",
  "Z11K1": "Z1002",
  "Z11K2": "new string"
}

We can change the content of the Z11K1 key to a literal language by invoking the changeType action with row ID 5 and type Z60 (Language):

changeType( {
  id: 5,
  type: 'Z60'
} );

This action will first create a blank structure for an object of type Z60, and insert it under the row ID 5, after clearing it of its current children nodes. This would result in the object (in canonical form, for simplicity):

{
  "Z1K1": "Z11",
  "Z11K1": {
     "Z1K1": "Z60",
     "Z60K1":  "",
     "Z60K2": [ "Z6" ]
  },
  "Z11K2": "new string"
}

Other actions

Other (less widely used, but also important) actions are:

  • setZFunctionCallArguments: When changing a Function call Function ID/Z7K1, this action clears the arguments of the previous Function ID, looks for the input keys and types of the new Function Id, and sets the necessary keys initialized to their blank values.
  • setZImplementationContentType: When changing an Implementation/Z14 from type Code to Composition, this action clears the key from the previous configuration and sets the new key. For example, when changing from Composition to Code, removes the key Z14K2 and its child nodes, and sets Z14K3 initialized to a blank Code/Z16 object.
Getters

Getters are the most important part of the ZObject module and what allows every component to fetch the data. Some of these getters are quite nuclear and operate from a row ID and without any knowledge of what type of ZObject they are accessing. The most important nuclear getters are:

  • getRowById: Given a row ID, searches for this id in the ZObject table and returns the Row object for this ID. Every getter uses this.
  • getChildrenByParentRowId: Given a row ID, returns all the child Row objects (or, what is the same, returns all the Rows that have the parent property equal to the given row ID). This getter is used every time that we need to navigate down the ZObject tree.
  • getRowByKeyPath: Given a row ID and an array of keys, returns the Row resulting of the navigation down the tree, starting from the initial row ID, and following the sequence of keys. For example, if a Monolingual string start at row ID 1, getRowByKeyPath( 1, [ "Z11K2", "Z6K1" ] ) will return the Row where the key is "Z6K1" and is grandchild of row ID 1.

These three nuclear getters allow for navigation through the ZObject tree, and are used for more complex getters for particular types such as:

  • Given the row ID of a String/Z6:
    • getZStringTerminalValue: Returns the value of the String value/Z6K1 key
  • Given the row ID of a Reference/Z9:
    • getZReferenceTerminalValue: Returns the value of the Reference ID/Z9K1 key
  • Given the row ID of a Monolingual text/Z11:
    • getZMonolingualLangValue: Returns the value of the Language/Z11K1 key
    • getZMonolingualTextValue: Returns the value of the Text/Z11K2 key
    • And so on...

It is convenient to create as many typed getters as needed when creating components for particular types.

Default View and Component tree

A ZObject is a JSON object that follows the Wikifunctions Function Model structure specifications.

ZObjects are essentially trees, where at every level:

  • There is one key Z1K1 that defines the type of the object represented at that level. This type also defines the rest of the keys that will be found in this ZObject.
  • Keys are unique for that level, and have the shape ZnKm where Zn is the type, and Km is the m'th key.
  • The values of these keys are also ZObjects, which can be:
    • Terminal ZObjects: Z6/String or Z9/Reference
    • Objects of any other type: Their type will, again, be specified under their own Z1K1 keys.
  • The values for each key are generally bound to be of a specific type. E.g. In an object of type Monolingual text/Z11, the value of its key Z11K1 must be a Human language/Z60, and the value of its key Z11K2 must be a String/Z6

To accommodate this structure, ZObject components in the front-end should:

  • Have a nested structure, so that contributors can explore and edit the ZObject in depth till reaching the terminal values.
  • Provide a collection of builtin components for most common types, that can render a user friendly interface for viewing or editing these objects.
  • Provide a fallback experience for any other types even if they don't have builtin components, which allows contributors to view and edit their values.

The Default View is a Vue page view that can accommodate any possible ZObject, allowing anyone to create, view or edit any kind of ZObject. The content section renders the main component tree, using the structural component ZObjectKeyValue.

The components needed for the Default View tree are:

  • Structural Components:
    • ZObjectKeyValue
    • ZObjectKeyValueSet
  • Utility Components:
    • ModeSelector
    • ExpandedToggle
  • Non-typed Components:
    • ZObjectToString
  • Typed Components:
    • For terminal types:
      • ZString
      • ZReference
    • For other types:
      • ZBoolean
      • ZMonolingualString
      • ZFunctionCall
      • ZTypedList
      • ZImplementation
      • ZArgumentReference
      • ZCode
      • ZTester
      • And more...

This documentation will cover the structural and utility components, a few of the most important non-typed and typed components, and a guide on how to create new components for a given type.

ZObjectKeyValue

The component ZObjectKeyValue.vue is the most important piece of the Default View as it represents every key-value pair and provides the following functionality:

  • It renders the key in the preferred user language, or in the closest fallback language. E.g. Instead of "Z1K1", it shows "type" or "(en) type"
  • If figures out the ZObject type of its value
  • It allows switching between collapsed and expanded mode (See ExpandedToggle section below)
  • Depending on the type and on the expanded/collapsed mode, it decides what component to render the value with.
  • Depending on the type specification for the given key, it restricts the changes that a contributor can make to the value.
  • It allows changing the mode of the value, between literal ZObject, a referenced ZObject, or a ZObject produced by a function call. (See ModeSelector section section below)
  • It handles changes in the value by catching and handling set-value and set-type events from the value. (See data modification events section below)

The ZObjectKeyValue component acts as the main functional node for the whole tree, while the typed components just handle the rendering of the HTML elements.

Every ZObjectKeyValue component will render:

ExpandedToggle

This component precedes every key-value, and it is a button with three states:

  • Bullet point: The value for this key doesn't have a collapsed view (a builtin component for that type), so it will always be shown using ZObjectKeyValueSet, with no possibility of switching between expanded/collapsed.
  • Collapsed, or right chevron: The value for this key is rendered with its collapsed view, which is a builtin component specific for its type. By clicking it, it will switch to expanded.
  • Expanded, or down chevron: The value for this key has a collapsed view (or a builtin component for its type), but it's currently shown in its expanded view (with the fallback ZObjectKeyValueSet component). By clicking it, it will switch to collapsed.

ModeSelector

File:Wikifunctions ModeSelector for list item.png
The ModeSelector component button and its expanded menu, for a key belonging to a list item.

Even when a given key must contain a value of a bound type (E.g. the key Z11K1 must only contain values of type Language/Z60), this value could be passed in different ways (or modes):

  • The value can be a literal object of the required type.
  • The value can be a link to an object of the required type.
  • The value can be a function call that returns an object of the required type.
  • Inside an implementation composition, the value can be a reference to an argument of the required type.

The ModeSelector component follows every key, and is a button with the icon of an ellipsis ..., with a dropdown menu with all the possible representations of the value. Clicking on any of these options, will fully change the shape of the value under this key to the selected one.

The available values are specific to the key, its required type and its context. This component is not rendered when:

  • It is a Read page.
  • The key is hidden in the parent ZObjectKeyValue component by setting skipKey=true.

Additionally to the mode editing options, when the key belongs to a list item, it adds an additional menu item to delete the item from the list.

ZObjectKeyValueSet

The ZObjectKeyValueSet component renders the collection of key-value pairs of a ZObject. Whenever showing a given ZObject using this component, it will iterate through all its keys, and render a ZObjectKeyValue component for each one of them.

This component is the fallback option when:

  • There is no builtin component associated with its type. E.g. An object of type Key/Z3 has no builtin component, so it will be rendered using ZObjectKeyValueSet, which will render a ZObjectKeyValue for each of its keys (Z1K1, Z3K1, Z3K2 and Z3K3).
  • There is a builtin component for this type, but the ZObjectKeyValue component is on expanded mode.

Typed components

Some common types have their own builtin components that understand the existing keys, can initialize values, and can render HTML elements that allow for easy viewing and editing of their values.

The components ZString and ZReference are terminal, and hence will not allow further expansion into ZObjectKeyValueSet. All the other typed components can be switched to expanded mode.

Every typed component should at least:

  1. Implement a template that adapts to either a Read or an Edit page.
  2. Implement the necessary getters that bring the relevant values from the ZObject table in the store.
  3. React to change events in the relevant template elements, and emit setValue events with the correct payload, so that the ZObjectKeyValue parent component updates the state.
  4. Additionally, any typed component should be able to handle an empty state.

For a good example, we can take a look at the ZMonolingualString component. The following figures show a list of Monolingual string/Z11 objects. The one for the Edit page also showcases the component blank state:

Read page Edit source page
File:List of Monolingual string components in Read mode.png File:List of Monolingual String components in Edit mode.png


This is how the ZMonolingualString component solves the points listed above:

  1. The template adds a chip for the language ISO code (corresponds to the key Z11K1), and a text for the string value (corresponds to the key Z11K2) for Read mode. In Edit mode, the string value is instead shown in an Input component, for it to be editable.
  2. The getter getZMonolingualLangValue returns the value of the Z11K1 key starting from the current row ID: This value is fed into the language template element. The getter getZMonolingualTextValue returns the value of the Z11K2 key starting from the current row ID: this value is fed into the text or input field in the template.
  3. The component observes changes in the input field, and when it changes, it emits the event setValue with the following payload:
    {
      keyPath: [ "Z11K1", "Z6K1" ],
      value: "new value"
    }
    
  4. The blank state of the monolingual string has a special style as seen in the image.


<translate> TODO:</translate> Do we need a section for TypedList related components?


The Component Tree

Let's look at an example of the component tree putting all of these together. Assuming a Persistent Object/Z2 containing a Monolingual text/Z11 as shown below (only the content key, for simplicity):

{
  "Z2K2": {
    "Z1K1": "Z11",
    "Z11K1": "Z1002",
    "Z11K2": "new string"
  }
}

This is the tree of components that would be rendered:

  • ZObjectKeyValue component for the key Z2K2:
    • properties:
      • key: Z2K2
      • expected type for key: Object/Z1 (anything)
      • value type: Monolingual text/Z11
    • component rendered for the value:
      • ZMonolingualString component, if expanded is false, or
      • ZObjectKeyValueSet component, if expanded is true.
        • ZObjectKeyValue component for the key Z1K1
          • properties:
            • key: Z1K1
            • expected type for key: Type/Z4
            • value type: Reference/Z9
          • component rendered for the value:
            • ZReference component
        • ZObjectKeyValue component for the key Z11K1
          • properties:
            • key: Z11K1
            • expected type for key: Language/Z60
            • value type: Reference/Z9
          • component rendered for the value:
            • ZReference component
        • ZObjectKeyValue component for the key Z11K2
          • properties:
            • key: Z11K2
            • expected type for key: String/Z6
            • value type: String/Z6
          • component rendered for the value:
            • ZString component

Data modification events

<translate> TODO:</translate> Write about how the components can handle their modification with setValue and setType. Describe what actions from the store manage state modifications of values and types.


  • When the value ZObject updates any of the values of its keys, without changing its whole type, sends a set-value event which is handled by the [???]
    • It captures set-value events from the value component and performs the modification of the ZObject in the store. This occurs when the value ZObject changes the value of any of its keys, without changing its type.
    • It captures set-type events from the value component and performs the modification of the ZObject in the store. This occurs when the value ZObject changes its type, so the keys are fully replaced with new ones.

Widgets

Page widgets are independent components that are part of the Views and provide an enrich experience to the main representation of the ZObject content. All Widgets are stored in the components/widgets directory, and use the base component Widget Base as base component.

About widget

File:Wikifunction About widget function page.png
About widget in the Function Viewer page
File:Wikifunctions About widget language selector dialog.png
The About widget language selector dialog allows users to also add metadata in new languages

The About widget is present in all view and edit pages, except for the Function Editor. It provides a way to view, navigate and edit multilingual information to the page ZObject.

When in the Function Viewer page, the About widget also displays information about the function inputs, their types and labels, and the function output type. Similarly, the Function page About widget also allows editing or adding input labels.

Even when the page is in Read mode, the About widget allows edition of multilingual data, although the edit and submission flow is different.

  • When in Edit mode: Users can add multilingual information into the Widget cummulatively, and press the button "Done". Only when clicking the page "Publish" button, all the changes will be submitted.
  • When in Read mode: Users can open the About widget form, navigate through languages, and make edits, but they will have to press "Continue to publish" directly from the dialog to persist their changes, or else they will be discarded.

The About widget is formed by the components:

  • About.vue: The base widget component with the selected metadata to show in the page.
  • AboutViewLanguagesDialog.vue: Dialog component to select a language or add a new one.
  • AboutEditMetadataDialog.vue: Dialog component to edit the metadata for the selected language.

Publish widget

The Publish widget is rendered in all edit pages and initiates validation, transformation and submission of edited or newly created ZObject. It is formed by the components:

  • Publish.vue: The base widget which renders the Cancel and Publish buttons. The "Publish" button is initially disabled, and is enabled only when changes are observed. It also triggers the validateZObject action when this button is clicked.
  • PublishDialog.vue: Dialog component that's shown if the validation is passed successfully. Displays any warning messages that might be relevant to the user's edit, the text field to add a publish summary, and the final submission or cancelation buttons. This triggers the final ZObject transformations and submission as detailed in the transformations section. If the submission is unsuccessful, this dialog will show any errors returned by the edit API.

Function Evaluator widget

File:Wikifunctions Function evaluator widget running successfully.png
Function Evaluator widget successfully running the If function

The Function Evaluator widget is shown in the Function Viewer page, and in the edit and view pages for Test/Z20 and Implementation/Z14 objects. It allows the user to try the function relevant to the page, as long as the Function is runnable (it has attached implementations) and the user has the right execute permissions.

The Function Evaluator widget runs the preferred implementation of the given function, except when in the Implementation view or edit page. In these cases, the Function Evaluator widget runs the particular Implementation represented in the page.

The widget uses the ZObjectKeyValue.vue default component which to create a component representation of both the function call and the returned result. For that purpose, the component needs both ZObjects (function call and response) to be stored as ZObject flat tables the way that the main page ZObject is (see more details in the ZObject table section). To accomplish this, the widget creates two different detached ZObjects in the table and saves their root row IDs, which are passed to the ZObjectKeyValue.vue components that represent the Function Call and the Function Call Response.

The store module callZFunction.js contains the actions necessary to initialize the widget and perform the function call.

Function Explorer widget

The Function Explorer widget allows users to see details of a Function definition, explore existing persisted Functions and see relevant details while writing an Implementation.

The widget is shown in the Read and Edit pages for Implementation/Z14 and Test/Z20 objects, and it has two different modes:

  • In Read mode: the widget shows information of the current Implementation's target Function. E.g. If we are viewing an Implementation for the function "If", the Function explorer shows the signature details of the function "If".
  • In Edit mode: the widget renders a search box where the user can search and select already existing functions, and see their signature details.

Function Report widget

The Function Report widget displays the current Test/Implementation status for the current Function. This widget is rendered in the Read and Edit pages for Implementation/Z14 and Test/Z20 objects:

  • In an Implementation/Z14 page: The Function Report widget shows the state of all the available Tests of the current function, running against the given Implementation.
  • In a Test/Z20 page: The Function Report widget shows the state of all the available Implementations for the current function, running the given Test.