-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.json
1 lines (1 loc) · 160 KB
/
index.json
1
[{"categories":null,"contents":"The Graphical Language Server Platform (GLSP) is a client-server framework for building web-based diagram editors. It follows an architectural pattern similar to the hugely popular Language Server Protocol, but applies it to graphical modeling and diagram editors. With that, GLSP enables the development of modern, web-based diagram editors, whereas the heavy lifting, such as loading, interpreting, and editing diagrams according to the rules of the graphical diagram language, is encapsulated in the server. Consequently, diagram editors can easily be integrated with multiple tool platforms, such as VS Code, Eclipse Theia, Eclipse RCP, and in standalone or web apps.\nA GLSP Server is a separate backend process and provides the language-specific smarts and editing capabilities. It communicates with a GLSP Client over JSON-RPC with an extensible action protocol. Each application has one GLSP client. A GLSP client can have multiple sessions, whereas each session represents a single diagram editor instance. Each client is handled by exactly one GLSP server. A server can handle multiple clients, each within a dedicated client session.\nHow it works The GLSP server is responsible for loading an arbitrary source model, e.g. a JSON file, EMF model, or database, and defines how to transform it into the graphical model. The graphical model is a serializable description of the diagram to be rendered on the client. It is the central communication artifact that is transferred to the client.\nThe GLSP client receives the graphical model as input and renders it. Additionally, the client requests information on editing operations that can be applied in the context of the graphical model. Based on this information, the client can provide the editing tools for making changes to the diagram, such as moving elements, adding nodes or edges, changing labels, etc.\nOnce a user performs a change in the diagram, the client sends a notification to the server. The server then applies the operation back to the source model, regenerates the graphical model and updates the client with the new version of the graphical model. The client will then update the rendered diagram based on the new graphical model it received.\nCustomization and Extension As diagram editors are by nature very specific to the respective diagram language, GLSP is designed with a strong focus on customizability and extensibility. To this end, GLSP applies two principles:\n Dependency Injection Both the client and the server are using an inversion of control pattern based on dependency injection (DI) with inversify.js and Guice. Every service and component is configured in a global DI container and can simply be extended or even replaced with a custom implementation. Adopters consequently have the same power for their diagram editors, as the framework authors. Slim abstractions and direct access to the underlying technologies Especially, when it comes to the look \u0026amp; feel of a diagram editor, it is crucial to have full control over the rendering and the UI. Therefore, GLSP gives full access and control over the underlying UI technologies, such as Eclipse Sprotty, SVG and CSS, editing tools, user interface controls (HTML), without abstraction layers where control matters. Avoiding abstraction layers also enables an excellent debugging experience. ➡️ Now it is time to get started!\n","permalink":"https://www.eclipse.dev/glsp/documentation/overview/","tags":null,"title":"Overview"},{"categories":null,"contents":"GLSP is architected to be very flexible and provides several implementation options:\n 🖥️ Server GLSP servers can be written either in Java or with Typescript based on nodejs. 🗂️ Source Model You can use any format or framework for managing your source model. For the most common choices, GLSP provides dedicated base modules, such as for EMF, emf.cloud, or GModel-JSON, which contains the graphical model directly. 🖼️ Tool Platform Diagram editors can used in multiple tool platforms or used in plain web applications or in an Electron app. While GLSP editors can be integrated into any web application, GLSP provides dedicated integration components for seamlessly deploying a GLSP editor inside of Eclipse Theia, VS Code, or Eclipse RCP. Due to GLSP\u0026rsquo;s architecture, you can even change any of those options above later on, without impacting other parts of your implementation, or support multiple variants, e.g. VS Code and Eclipse RCP, while sharing almost all of your server and client code.\nTo get you started quickly, GLSP provides project templates for the most popular choices. Thus, please clone the glsp-examples repository and switch to the folder project templates:\ngit clone https://github.com/eclipse-glsp/glsp-examples.git cd glsp-examples/project-templates Now select your preferred server language, source model format, and platform integration (see list below). Switch to the respective folder and follow its README file.\n 🖥️ Java ● 🗂️ EMF ● 🖼️ Theia \u0026ndash; java-emf-theia 🖥️ Node ● 🗂️ Custom JSON ● 🖼️ Theia \u0026ndash; node-json-theia 🖥️ Node ● 🗂️ Custom JSON ● 🖼️ VS Code \u0026ndash; node-json-vscode 🖥️ Java ● 🗂️ EMF ● 🖼️ Eclipse \u0026ndash; java-emf-eclipse If you would like to use an external source model management component, you can use the EMF.cloud Model Server component. Please see the following project-template and follow its README file.\n 🖥️ Java ● 🗂️ EMF.cloud Model Server ● 🖼️ Theia \u0026ndash; modelserver-glspjava-emf-theia If you don\u0026rsquo;t find your preferred combination, please raise a question in the Github discussions. If you need help on deciding which combination is right for you, please refer to the integrations page or look at our support options.\nWhat next? Once you are up and running based on the project template above, we recommend to start working on the following aspects next:\n ➡️ Add your custom source model instead of using the example model! ➡️ Define the diagram elements to be generated from the source model into the graphical model! ➡️ Make the diagram look the way you want by adjusting the diagram rendering and styling! ➡️ Look at the workflow example to explore the implementation of more advanced editor features ","permalink":"https://www.eclipse.dev/glsp/documentation/gettingstarted/","tags":null,"title":"Getting Started"},{"categories":null,"contents":"As mentioned in the getting started guide, GLSP is architected to be very flexible and provides several options. On this page, we give an overview of the dedicated integration components and point to the respective source code.\n 🖥️ Server GLSP servers can be written either in Java or with Typescript based on nodejs. 🗂️ Source Model You can use any format or framework for managing your source model. For the most common choices, GLSP provides dedicated base modules, such as for EMF, emf.cloud, or GModel-JSON, which contains the graphical model directly. 🖼️ Tool Platform Diagram editors can used in multiple tool platforms or used in plain web applications or in an Electron app. GLSP provides dedicated integration components for seamlessly deploying a GLSP editor inside of Eclipse Theia, VS Code, or Eclipse RCP. Due to GLSP\u0026rsquo;s architecture, you can even change any of those options above later on, without impacting other parts of your implementation, or support multiple variants, e.g. VS Code and Eclipse RCP, while sharing almost all of your server and client code.\nNeed help deciding? There are many options to choose from. In the following, we list a few hints to help you decide. Please note that especially the tool platform integration doesn\u0026rsquo;t have to be an ultimate decision. Many adopters start deploying for one tool platform, e.g. Eclipse Theia, but add support for VS Code later and offer both options in parallel.\n 🖥️ Server Whether to use Java or Typescript is a matter of taste. However, there also are objective considerations.\n If you aim at providing a VS Code extension or Theia plugin, shipping a node-based GLSP server is easier, because you don\u0026rsquo;t need to worry whether your users have a JVM installed. They\u0026rsquo;ll have a node runtime already, otherwise they couldn\u0026rsquo;t run VS Code or Theia. If you have more control over your users runtime, e.g. because the editor will run in a container in the cloud anyway, this is less of a concern. Using one language (Typescript) for your GLSP server and client leads to a more homogeneous developer experience than having to work with two languages (Java and Typescript). If you already have an editor implementation in Java, e.g. based on GMF, etc., or an EMF metamodel, building a Java-based server opens lots of opportunities for reusing your existing business logic in the GLSP server. If you want to work with EMF source models directly from your GLSP server, e.g. because you want to benefit from its command stack implementation, change notification support, etc., using Java on the GLSP server is clearly better. 🗂️ Source Model The choice of a framework to manage your source model mostly depends on two things:\n Which language do you choose for your GLSP server? This may already remove options, e.g. EMF in a node server, unless you externalize the model management into an own separate component, such as an emf.cloud model server. How do you process those source models later on, e.g. for code generation, interpreting it at runtime, etc. and what in what language those components that process your source model are developed with? Besides, there are a few more considerations.\n Usually you want to provide undo/redo support. If you use a custom serialization and format, you\u0026rsquo;ll have to implement that yourself. Or are there frameworks available that will do that for you? For Java, EMF is a great choice here. You may want to consider using a database instead of a file. This will externalize much of the model and transaction management for you. However, databases have the disadvantage that they shouldn\u0026rsquo;t be checked into a git repository. So you opt out of a completely file-based approach with that choice. In many use cases, it is advisable to separate the graphical information (such as coordinates) from the \u0026ldquo;semantic\u0026rdquo; information of your model. This way your semantic model will be more concise for later processing. It\u0026rsquo;ll put a slight overhead during editing on top, as you\u0026rsquo;ll need to manage two resources. For EMF, GLSP provides a very simple notation model to capture the graphical information. More information on the integration components is given in the section on source model integrations.\n🖼️ Tool Platform The decision for a tool platform has many aspects, such as are you providing a product or a plugin for a generic tool, such as VS Code, are your users already using a certain tool platform, etc.? However, the integration layer of GLSP editors for certain tools is rather thin and it is not much work to provide multiple options here in parallel. So choose what\u0026rsquo;s best for you now, you can easily change or add a tool platform support later.\nMore information on the integration components is given in the section on platform integrations.\n Package Architecture Depending on your choice of tool platform integration and server framework, a different selection of packages needs to be used. The project templates linked in the getting started guide provide the initial setup of the package architecture for the respective combination of components. However, all of them will have a diagram-specific client package that depends on @glsp/client and a diagram-specific server package that either depends on the node-based GLSP server framework or the Java-based GLSP server framework.\nIrrespectively of the used tool platform integration, server framework or source model integration, your custom glsp-client is always the same and can be reused for all scenarios. Your server implementation is also independent of the respective platform integration and reusable for multiple platforms. Depending on the source model framework, the server may add additional dependencies (e.g. to use the EMF.cloud model server client).\nAs an example, the following figure shows the package architecture for a Theia-based GLSP editor with a node-based server.\n Package overview for node-based server and Theia integration The package your-glsp-client represents your custom client package and your-glsp-server depicts your custom GLSP server package. They contain the diagram-specific implementations for your diagram editor and modeling language.\nPlease note how the your-glsp-client builds upon the @glsp/client and the package your-theia-integration just integrates this as an editor based on @glsp/theia-integration into the Theia tool platform. Your GLSP client and your Theia integration have an indirect dependency to Eclipse Sprotty and its Theia glue code. Both the client and the server share a common package @glsp/protocol that defines the action types.\nServer Frameworks GLSP servers can be written in any language, as they run in a separate process and communicate via JSON-RCP with the client. To make it easier to develop GLSP servers, however, GLSP provides two server frameworks:\n GLSP server framework for Java GLSP server framework for Node Even though they are built with different runtimes and languages, they are structurally very similar. Both use dependency injection (DI) for hooking up your diagram-specific providers, services, and handlers or for replacing default implementation with customized implementations.\nThe Java-based GLSP server uses Google Guice as a dependency injection framework. With Google Guice, there is one main DI module that contains each binding in a dedicated method. Adopters can extend this module and customize it by overriding dedicated binding methods. The node-based GLSP server uses inversify.js as dependency injection framework.\nFor both servers, GLSP provides dedicated abstract base classes named DiagramModule, which are intended to be extended in order to implement a concrete diagram server. The idea of those abstract base classes is that the abstract methods they contain MUST be implemented in order to show a diagram, e.g. the source model storage and the graphical model factory, and additional methods MAY be overwritten to add functionalities, such as certain editing operations or model validation, or to customize default behavior. There are also pre-configured diagram modules for certain source models, described below, e.g. for EMF or EMF.cloud, which already bind relevant implementations.\nThe remainder of this documentation shows, whenever applicable, a code example for both servers. Also there are project templates for both servers, as listed in the getting started guide, as well as an example server for the common \u0026ldquo;workflow diagram\u0026rdquo;, in each of the server repositories, linked above.\nServer DI Container vs Diagram Session DI Container It is worth noting, that GLSP servers distinguish between two DI containers:\n Server DI Container The server DI container is used to configure global components and services that are not related to a specific session diagram as well as components that are shared between all session containers. Diagram Session DI Containers Each client session has a dedicated DI container which holds the session-specific information as well as dedicated instances of handlers, states, etc. Each client session container is associated with a single diagram language and is configured with the corresponding diagram module. Source Model Integrations GLSP works with any source model format or framework for managing your source model, as the implementation for loading source models and translating them into diagrams needs to be provided by the developer of the diagram editor. However, there are recurring popular choices, for which GLSP provides base modules with default implementations for a specific source model framework.\n Integration for EMF models Integration for EMF models with a separate notation model Integration for EMF.cloud model server Abstract base diagram module for storing and modifying GModels as JSON files directly: Java or node Platform Integrations GLSP-based editors can be integrated into any web application frame. To ease the platform integration for adopters, however, dedicated glue code frameworks are provided for\n Creating an extension to provide a GLSP editor for Eclipse Theia Creating an extension to provide a GLSP editor for VS Code Creating a plugin to prove a GLSP editor for the Eclipse IDE Creating a standalone GLSP web editor based on plain javascript In general it is recommended to keep the GLSP diagram implementation separated from the platform integration code by splitting them into separate packages. With that, the core GLSP editor can be easily reused and integrated into another platform.\nAs an example, the GLSP Workflow example provides the GLSP diagram implementation in the @eclipse-glsp/workflow-glsp package. All platform-specific integration examples import this package and provide a small integration package containing the platform-specific glue code on top.\n","permalink":"https://www.eclipse.dev/glsp/documentation/integrations/","tags":null,"title":"Servers \u0026 Integrations"},{"categories":null,"contents":"Source Model The source model represents the actual data that is represented in the diagram and that is modified when the user applies changes in the diagram. Typical source model formats are EMF models, JSON files, and databases, etc. However, GLSP and the GLSP server frameworks don\u0026rsquo;t put any restrictions on what the format of this source model is. This is achieved by putting developers of GLSP diagram servers in charge of defining how to load a source model, how to transform it into a graphical model, which is the description of the diagram to be rendered, and how to manipulate the source model, if a user edits a diagram.\nTherefore, a GLSP server needs to provide the following implementations:\n Source model storage, which defines how to load and store source models. Graphical model factory, which defines how the source model is transformed into a graphical model. Edit operation handlers that manipulate the source model, based on user actions performed in the diagram. Loading Source Models and Showing a Diagram To load a source model and show a diagram, the following steps are performed:\n The client sends a RequestModelAction with a URI or other arguments for identifying a source model to the server The server invokes the source model storage to load the source model identified by the arguments sent by the client The server invokes the graphical model factory to translate the source model into the graphical model The server sends the created graphical model to the client The client renders graphical model Processing Edit Operations When a user performs an edit operation in the diagram:\n The client sends an operation request to the server The server invokes the registered edit operation handler, which modifies the underlying source model directly The server applies the graphical model factory to the modified source model to create a new graphical model The server sends the created graphical model to the client The client re-renders the diagram according to the new version of the graphical model As can be seen in the steps above for loading and editing source models, both processes share many steps and are based on the same three custom implementations for particular source models. Thus, by providing these three implementations, any source model format can be supported with GLSP.\nPlease note that GLSP provides generic base implementations for typical source model types.\nSource Model Storage and Model State Every GLSP server needs to provide an implementation of the interface SourceModelStorage. Implementations of this interface are responsible for loading source models from a specific resource, such as an EMF model, a JSON file, or a database, into the GLSP server\u0026rsquo;s model state.\nThe ModelState is the central stateful object within a client session that represents the information about the current state of the original source model. All other services and handlers may access the model state to obtain the required information about the model in order to perform their diagram editing tasks.\nAs you typically need to store custom information that is specific to your model in the model state, you typically also bind a custom implementation that represents your model state. This implementation usually implements the interface GModelState (and extends the base class DefaultGModelState).\nJava GLSP Server protected Class\u0026lt;? extends GModelState\u0026gt; bindGModelState() { return MyModelState.class; } Node GLSP Server protected override bindModelState(): BindingTarget\u0026lt;ModelState\u0026gt; { return MyModelState; } Now as we have registered our model state implementation, we can look at the source model loading. First, we need to make the GLSP server aware of your SourceModelStorage implementation, so you have to bind your implementation of the SourceModelStorage interface in the server’s DI module:\nJava GLSP Server @Override protected Class\u0026lt;? extends SourceModelStorage\u0026gt; bindSourceModelStorage() { return MySourceModelStorage.class; } Node GLSP Server protected bindSourceModelStorage(): BindingTarget\u0026lt;SourceModelStorage\u0026gt; { return MySourceModelStorage; } The registered implementation of the source model storage needs to provide two functionalities:\n Loading source models, based on the parameters that are contained in the RequestModelAction, and adding them into the session’s model state. The implementation mostly depends on where you need to load your source model(s) from and what kind of model(s) you are dealing with (files, XML, JSON, EMF, a database, etc.). Saving the current version of the source model from the GModelState back into its original resource (files, XML, EMF, database, etc.). This method is invoked when the client sends a SaveModelAction. Java GLSP Server public class MySourceModelStorage implements SourceModelStorage { @Inject protected MyModelState modelState; @Override public void loadSourceModel(final RequestModelAction action) { final String uri = MapUtil.getValue(action.options, \u0026#34;sourceURI\u0026#34;); // load your model from file, EMF, database, etc. final YourModel model = …; // add information needed about your model into the model state modelState.setModel(model); } @Override public void saveSourceModel(final SaveModelAction action) { // get the current version of your model final YourModel model = modelState.getModel(); // get the information to know where to store your model final String uri = action.getFileUri().get(); try { // store your model } catch (IOException e) { LOG.error(e); throw new GLSPServerException(\u0026#34;An error occurred while saving the model.\u0026#34;, e); } } } Node GLSP Server @injectable() export class MySourceModelStorage implements SourceModelStorage { @inject(MyModelState) protected modelState: MyModelState; loadSourceModel(action: RequestModelAction): MaybePromise\u0026lt;void\u0026gt; { const uri = action.options![\u0026#34;sourceURI\u0026#34;]; // load your model from file, EMF, database, etc. const model = …; // add information needed about your model into the model state this.modelState.model=model; } saveSourceModel(action: SaveModelAction): MaybePromise\u0026lt;void\u0026gt; { // get the current version of your model const model = this.modelState.model; // get the information to know where to store your model const uri = this.modelState.sourceUri; try { // store your model } catch (error) { throw new GLSPServerError(`Could not load model from file: ${this.modelState.sourceUri}`, error); } } } The GLSP Workflow example is an example, in which in which the source model is a JSON file that contains the GModel directly. In such a scenario, you can use the plain GModelState and the JsonFileGModelStorage.\nOnce the source model has been loaded into the model state, the GLSP server invokes the configured GModelFactory to derive the graphical model from the source model and issues model update for the client.\n ➡️ Let\u0026rsquo;s look at how the source model is translated into a graphical model next!\n","permalink":"https://www.eclipse.dev/glsp/documentation/sourcemodel/","tags":null,"title":"Source Model \u0026 State"},{"categories":null,"contents":"The graphical model is a serializable description of the diagram to be visualized on the client. It is the central communication artifact between client and server. The server creates the graphical model from an arbitrary source model by invoking a so-called GModelFactory and sends the graphical model to the client. Thus, the client doesn\u0026rsquo;t need to know from which source model it has been generated and how to manipulate the source model. However, the client interprets the graphical model in order to render a visualization of the diagram described in the graphical model.\nGraphical Model Structure The graphical model is composed of elements and edges. Each element or edge has a unique identifier and a type. The graphical model elements are organized in a tree, as defined by the parent-child relationship between elements, with a single root element. The graphical model library consists of several common base classes, such as nodes, ports, labels, compartments, but can be extended with additional properties or even new types, if needed.\nThe graphical model is typically composed of the following elements.\n GModelRoot: Each graphical model must have exactly one root GShapeElement: A graphical element is represented by a shape with visual bounds (position and size). Note that such elements can be nested based on their parent-child relationship. There are a the following concrete sub-types of shapes: GNode: Representation of a logical diagram node GPort: Ports are typically children of nodes and serve as connection points for edges GLabel: Representation of a text label GCompartment: A generic container element used for element grouping GEdge: A diagram edge that connects a source element and a target element (typically nodes or ports). Graphical model on the client The default GLSP client uses Sprotty, an SVG-based diagramming framework, to render diagrams. Sprotty uses a model to represent a diagram too \u0026ndash; the so-called SModel. The graphical model of GLSP is based on the SModel and, thus, can be seen as a compatible extension of the Sprotty model. To ensure a consistent development experience GLSP aliases all reused SModel types to the GModel namespace. For instance sprotty\u0026rsquo;s SModelElementImpl is equivalent to GLSP\u0026rsquo;s GModelElement. There should almost never be a reason to directly use the sprotty types. If possible always try to use the GModel API when developing on the client.\nGModel: Graphical model on the server The Java-based GLSP server uses EMF to represent and manage the graphical model internally. Note that this is just an internal way of representing the GModel at runtime but doesn’t mean that adopters need to represent their original source models with EMF too. The GLSP server uses EMF in order to reuse its model management and editing capabilities, its command stack and command-based editing. Therefore, the graphical model is described as an Ecore model and the corresponding Java classes are automatically generated from this model. Using GSON, the GModel is then serialized and deserialized to JSON before it is sent via JSON-RPC to the client.\nThe node-based GLSP server provides a graph model library, which defines the graph model types, such as GNode, GEdge, etc. alongside a builder API to make creating instances more convenient. However, as the node-based GLSP server and the GLSP client are both based on ES6, this graph library is based on graph model definitions that are used on the client.\nGraphical Model Factory After the initial loading of the source model – and also after each change of the source model – the GLSP server generates a graphical model from the source model in order to define what is to be rendered on the client.\nThe generation of the graphical model from the original source model is the responsibility of the GModelFactory. Therefore, the GModelFactory obtains the source model from the model state and generates a new graphical model from it. Implementations of the GModelFactory are by nature very specific to the source model and the diagram type. Thus, in almost every GLSP editor project, a custom GModelFactory implementation is provided.\nThe only exception are GLSP editors that directly operate on GModels; that is, the GModel is persisted and loaded directly by the registered implementation of the source model storage. In such cases, no transformation from the source model to the GModel needs to be provided as the source model already is the GModel. Thus, the so-called NullImpl of the GModelFactory can be used. An example for such a use case is provided in the GLSP Workflow example.\nFor all other use cases, an implementation of the GModelFactory needs to be provided and registered in the server DI module as follows.\nJava GLSP Server @Override protected Class\u0026lt;? extends GModelFactory\u0026gt; bindGModelFactory() { return MyModelFactory.class; } Node GLSP Server protected override bindGModelFactory(): BindingTarget\u0026lt;GModelFactory\u0026gt; { return MyModelFactory; } For the sake of an example, let’s assume that the source model is a simple list of named entities. Each entity should be visualized as a node with a label, which indicates its name. Then the corresponding ModelFactory could look as follows.\nJava GLSP Server public class MyModelFactory implements GModelFactory { @Inject protected MyModelState modelState; @Override public void createGModel() { List\u0026lt;Entity\u0026gt; entities = modelState.getModel().getEntities(); List\u0026lt;GModelElement\u0026gt; entityNodes = entities.stream().map(entity -\u0026gt; // new GNodeBuilder(\u0026#34;node:entity\u0026#34;) .layout(\u0026#34;vbox\u0026#34;) .add(new GLabelBuilder() .text(entity.getName()) .build()) .build()) .collect(Collectors.toList()); GGraph newModel = new GGraphBuilder() .id(\u0026#34;entity-graph\u0026#34;) .addAll(entityNodes) .build(); modelState.updateRoot(newModel); } } Node GLSP Server @injectable() export class MyModelFactory implements GModelFactory { @inject(MyModelState) protected modelState: MyModelState; createModel(): void { const entities = this.modelState.getModel().getEntities(); const entityNodes = entities.map((entity) =\u0026gt; new GNodeBuilder(GNode) .id(\u0026#34;node:entity\u0026#34;) .layout(\u0026#34;vbox\u0026#34;) .add(new GLabelBuilder(GLabel).text(entity.name).build()) .build() ); const newModel = new GGraphBuilder(GGraph) .id(\u0026#34;entity-graph\u0026#34;) .addChildren(...entityNodes) .build(); this.modelState.updateRoot(newModel); } } In the createGModel() method the entities are retrieved from the model state, as they have been added there by the source model storage (see Source Model Storage). Then a new GNode is created for each entity. Finally all new nodes are added as children of a newly created GGraph and the graphical root element in the model state is updated.\nNote that we have used the GModelBuilder API in this example to construct new graphical elements. This builder API offers a convenient way to construct new graphical model elements in a concise and fluent fashion. It is the preferred method and should be used over plain constructor creation.\nExtending the Graphical Model GLSP provides a set of default graphical model element classes that can be used to construct the graphical model and already cover a large set of use cases. For advanced use cases the existing base model elements can be customized or additional elements can be introduced. As an example, let’s have a look at the custom WeightedEdge element introduced by the GLSP Workflow example.\nGLSP Client A WeightedEdge is a special edge that has an optional “probability” property. We can define such an element by simply subclassing the GEdge class:\nexport class WeightedEdge extends GEdge { probability?: string; } And then the new WeightedEdge type has to be configured in the diagram module (di.config.ts).\nconst workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) =\u0026gt; { ... configureModelElement(context, \u0026#39;edge:weighted\u0026#39;, WeightedEdge, WorkflowEdgeView); ... } Node GLSP Server For the node GLSP server the new WeightedEdge type can be declared similar to the GLSP client by subclassing the GEdge class.\nexport class WeightedEdge extends GEdge { probability?: string; } To use the builder API for WeightedEdge creation we also have to implement a WeightedEdgeBuilder that extends the default GEdgeBuilder.\nexport class WeightedEdgeBuilder\u0026lt; E extends WeightedEdge = WeightedEdge \u0026gt; extends GEdgeBuilder\u0026lt;E\u0026gt; { probability(probability: string): this { this.proxy.probability = probability; return this; } } Java GLSP Server When using the Java GLSP server, a new Ecore model that extends the default \u0026ldquo;graph.ecore\u0026rdquo; model has to be created to declare new model elements. For more details, please have a look at the \u0026ldquo;workflow-graph.ecore\u0026rdquo; model in the GLSP Workflow example. Once the WeightedEdge is specified in the Ecore model, the corresponding source code has to be generated. Now the GraphExtension API can be used to configure the \u0026ldquo;workflow-graph.ecore\u0026rdquo; for the workflow diagram language. A class that implements the the corresponding interface has to created:\npublic class WFGraphExtension implements GraphExtension { @Override public EPackage getEPackage() { return WfgraphPackage.eINSTANCE; } @Override public EFactory getEFactory() { return WfgraphFactory.eINSTANCE; } } And then configured in the WorkflowDiagramModule:\n@Override protected Class\u0026lt;? extends GraphExtension\u0026gt; bindGraphExtension() { return WFGraphExtension.class; } To use the builder API for WeightedEdge creation we also have to implement a WeightedEdgeBuilder that extends the default AbstractGEdgeBuilder. \nGeneric Args Every graphical model element type has a generic “args” property, which can be used to store additional properties as key-value pairs. These arguments can be used as a more lightweight alternative to extending the graphical model classes, especially if only simple extensions are needed.\n ➡️ Let\u0026rsquo;s look at how the graphical model is rendered on the client next!\n","permalink":"https://www.eclipse.dev/glsp/documentation/gmodel/","tags":null,"title":"Graphical Model"},{"categories":null,"contents":"Rendering The input of the diagram rendering on the client is the GModel that has been generated on the server from the source model (see Graphical Model) and sent to the client via a SetModelAction or UpdateModelAction. The client is then responsible for rendering the GModel.\nIn order to render the received graphical model, each graphical element type needs to be associated with a view on the client. A view defines how a specific type of graphical element shall be transformed into a corresponding SVG representation. The derived SVG elements are then rendered on the canvas of the diagram widget.\nTo define a new view, we have to create a class that implements the IView interface and register it for a specific type that is used in the graphical model. As an example, let’s configure that the view named GLabelView is used for all elements with the type “label:custom”. Therefore, we first need to create a dependency injection module, named customDiagramModule below, and configure the GLabelView for the graphical model element type “label:custom” using the configureModelElement() utility function:\nconst customDiagramModule = new ContainerModule( (bind, unbind, isBound, rebind) =\u0026gt; { const context = { bind, unbind, isBound, rebind }; configureModelElement(context, \u0026#34;label:custom\u0026#34;, GLabel, GLabelView); } ); The configureModeElement() function takes the inversify binding context, the graphical model type, its model class and its associated view as input. Under the hood this function sets up the necessary bindings so that the GLSP client knows that\n Graphical model elements (received from the GLSP Server) with type ‘label:custom’ are deserialized to instances of GLabel Graphical model element with type ‘label:custom’ are rendered with the GLabelView In order to be effective, we need to load the module customDiagramModule defined above in the diagram DI container, aka the root \u0026ldquo;di.config.ts\u0026rdquo; of your diagram implementation. With that, every element of type “label:custom” will be rendered with the view implementation GLabelView.\nViews themselves are typically implemented with JSX, which simplifies the definition of SVG elements in Typescript. Therefore, the following generic imports are required in any module declaring a view to enable declaration of svg elements with JSX:\n/** @jsx svg */ import { VNode } from \u0026#34;snabbdom\u0026#34;; import { RenderingContext, svg } from ‘@eclipse-glsp/client’; In addition, make sure that the following options are set in the tsconfig.json file of your project:\n{ \u0026#34;compilerOptions\u0026#34;: { \u0026#34;jsx\u0026#34;:\u0026#34;react\u0026#34;, \u0026#34;reactNamespace\u0026#34;:\u0026#34;JSX\u0026#34; } With that, we can implement a view as follows:\n@injectable() export class GLabelView extends ShapeView { render( label: Readonly\u0026lt;GLabel\u0026gt;, context: RenderingContext ): VNode | undefined { if (!isEdgeLayoutable(label) \u0026amp;\u0026amp; !this.isVisible(label, context)) { return undefined; } const vnode = \u0026lt;text class-sprotty-label={true}\u0026gt;{label.text}\u0026lt;/text\u0026gt;; const subType = getSubType(label); if (subType) { setAttr(vnode, \u0026#34;class\u0026#34;, subType); } return vnode; } } Every view has to implement the render() method. The render() method takes the graphical model element as input and returns the corresponding SVG element as virtual DOM node. The viewer queries all registered views and creates a new virtual DOM which is then used to patch the current DOM of the diagram widget.\nNote that the GLabelView also checks whether the given element is visible and skips the SVG generation if the element is not visible in the diagram canvas. This check is optional but it’s highly recommended to implement it in your custom views as it heavily improves the rendering performance.\n\nDefault Views The following sections give an overview of available default views in Sprotty and GLSP and how to configure them. All of them are default model elements, which is already configured in the baseViewModule, but for the sake of completeness we list the configuration of the elements in the collapsible example code blocks.\nDefault Sprotty Views The following views are provided by the base framework Sprotty.\nCircularNodeView A CircularNodeView creates a round shape with a radius computed from the shape\u0026rsquo;s size (by default it computes the radius by the minimum of the shape\u0026rsquo;s width or height and divides that by 2). The computation of the radius can be overridden and adapted to custom needs.\nCircular nodes with a radius of `17.5` (1), `42.5` (2) and `7.5`. Example implementation Java GLSP Server new GNodeBuilder() .type(DefaultTypes.NODE_CIRCLE) .position(point.orElse(GraphUtil.point(0, 0))) .size(GraphUtil.dimension(15, 15)) .build(); Node GLSP Server GNode.builder() .type(DefaultTypes.NODE_CIRCLE) .position(point ?? Point.ORIGIN) .size(15, 15) .build(); The circular node element and its view are configured as follows:\nconfigureModelElement( context, DefaultTypes.NODE_CIRCLE, CircularNode, CircularNodeView ); DiamondNodeView A DiamondNodeView creates a rhombus shape based on the shape\u0026rsquo;s size.\nDiamond nodes with dimensions of `(25,25)` (1), `(20,41)` (2) and `(66,33)`. Example implementation Java GLSP Server new GNodeBuilder() .type(DefaultTypes.NODE_DIAMOND) .position(point.orElse(GraphUtil.point(0, 0))) .size(GraphUtil.dimension(25, 25)) .build(); Node GLSP Server GNode.builder() .type(DefaultTypes.NODE_DIAMOND) .position(point ?? Point.ORIGIN) .size(25, 25) .build(); The diamond node element and its view are configured as follows:\nconfigureModelElement( context, DefaultTypes.NODE_DIAMOND, DiamondNode, DiamondNodeView ); ExpandButtonView The ExpandButtonView renders a SVG element in the shape of a triangle that allows expandable parent elements to trigger expansion, for example to display further element information.\nA rectangular node with an expandable button that renders additional elements if expanded (right). Example implementation Java GLSP Server return new GNodeBuilder() .type(\u0026#34;node:expandable\u0026#34;) .addCssClass(\u0026#34;node-expandable\u0026#34;) .position(point.orElse(GraphUtil.point(0, 0))) .layout(GConstants.Layout.HBOX) .layoutOptions(new GLayoutOptions().hGap(15)) .add(new GLabelBuilder().text(\u0026#34;Expand\u0026#34;).build()) .add(new GButtonBuilder() .type(DefaultTypes.EXPAND_BUTTON) .addCssClass(\u0026#34;button-expand\u0026#34;) .enabled(true) .build()) .build(); Node GLSP Server GNode.builder() .type(\u0026#34;node:expandable\u0026#34;) .addCssClass(\u0026#34;node-expandable\u0026#34;) .position(point ?? Point.ORIGIN) .layout(\u0026#34;hbox\u0026#34;) .addLayoutOption(\u0026#34;hGap\u0026#34;, 15) .add(GLabel.builder().text(\u0026#34;Expand\u0026#34;).build()) .add( GButton.builder() .type(DefaultTypes.BUTTON_EXPAND) .addCssClass(\u0026#34;button-expand\u0026#34;) .enabled(true) .build() ) .build(); First, we start with the definition of an expandable node element and view.\nDefine the node element supporting the expandable feature:\nexport class ExpandableNode extends RectangularNode implements Expandable { static override readonly DEFAULT_FEATURES = [ ...GNode.DEFAULT_FEATURES, expandFeature, ]; expanded = false; // initially the node is collapsed } The expandable node view renders an additional text element if the node is expanded and also increases its height:\n@injectable() export class ExpandableNodeView extends ShapeView { render( node: Readonly\u0026lt;ExpandableNode\u0026gt;, context: RenderingContext, args?: IViewArgs ): VNode | undefined { if (!this.isVisible(node, context)) { return undefined; } return ( \u0026lt;g\u0026gt; \u0026lt;rect class-sprotty-node={node instanceof GNode} class-mouseover={node.hoverFeedback} class-selected={node.selected} x=\u0026#34;0\u0026#34; y=\u0026#34;0\u0026#34; width={Math.max(node.size.width, 0)} height={Math.max( node.expanded ? node.size.height + 20 : node.size.height, 0 )} \u0026gt;\u0026lt;/rect\u0026gt; {context.renderChildren(node)} {node.expanded \u0026amp;\u0026amp; ( \u0026lt;text x=\u0026#34;50\u0026#34; y=\u0026#34;45\u0026#34;\u0026gt; More information \u0026lt;/text\u0026gt; )} \u0026lt;/g\u0026gt; ); } } Now we can configure an expandable node element and view for the type \u0026quot;node:expandable\u0026quot;:\nconfigureModelElement( context, \u0026#34;node:expandable\u0026#34;, ExpandableNode, ExpandableNodeView ); The expand button element and its view can be configured with defaults as follows:\nconfigureModelElement( context, DefaultTypes.BUTTON_EXPAND, GButton, ExpandButtonView ); Finally, to handle the expansion toggle of the button, we register an IActionHandler for the respective sprotty action CollapseExpandAction:\nbind(ExpandHandler).toSelf().inSingletonScope(); configureActionHandler(context, CollapseExpandAction.KIND, ExpandHandler); @injectable() export class ExpandHandler implements IActionHandler { @inject(TYPES.SelectionService) protected selectionService: SelectionService; expansionState: { [key: string]: boolean } = {}; handle(action: Action): void { switch (action.kind) { case CollapseExpandAction.KIND: this.handleCollapseExpandAction(action as CollapseExpandAction); break; } } get modelRoot(): Readonly\u0026lt;SModelRoot\u0026gt; { return this.selectionService.getModelRoot(); } protected handleCollapseExpandAction(action: CollapseExpandAction): void { action.expandIds.forEach((id) =\u0026gt; (this.expansionState[id] = true)); action.collapseIds.forEach((id) =\u0026gt; (this.expansionState[id] = false)); this.applyExpansionState(); } protected applyExpansionState(): void { // eslint-disable-next-line guard-for-in for (const id in this.expansionState) { const element = this.modelRoot.index.getById(id); if (element \u0026amp;\u0026amp; element instanceof GParentElement \u0026amp;\u0026amp; element.children) { const expanded = this.expansionState[id]; (element as any).expanded = expanded; } } } } ForeignObjectView The ForeignObjectView renders elements that are foreign to SVG, such as HTML, MathML, etc. as specified in their namespace and code property. Usually such an element is contained by a node view that enables features, such as resizing and moving of the element.\nMulti line text box, using xhtml. Example implementation A common example use case for using a ForeignObjectView is to benefit from word wrapping support of HTML to show multiline text box. Therefore we would create a custom text node (which extends the GForeignObjectElement) which is contained by a parent node (hence we will configure both as node types in the diagram configuration).\n Java GLSP Server String multiLineComment = \u0026#34;Lorem ipsum dolor sit amet, consectetur adipiscing elit.\\n\u0026#34; + \u0026#34;Nam at tellus quis lacus auctor congue vel sit amet lectus.\\n\u0026#34; + \u0026#34;Cras interdum lectus vel enim mollis maximus.\u0026#34;; new GNodeBuilder(\u0026#34;comment-node-parent\u0026#34;) .addCssClass(\u0026#34;comment-node-parent\u0026#34;) .size(GraphUtil.dimension(720, 125)) .position(point.orElse(GraphUtil.point(0, 0))) .add(new GNodeBuilder(\u0026#34;comment-node\u0026#34;) .addArgument(\u0026#34;text\u0026#34;, multiLineComment) .build()) .build(); Node GLSP Server const multiLineComment = \u0026#34;Lorem ipsum dolor sit amet, consectetur adipiscing elit.\\n\u0026#34; + \u0026#34;Nam at tellus quis lacus auctor congue vel sit amet lectus.\\n\u0026#34; + \u0026#34;Cras interdum lectus vel enim mollis maximus.\u0026#34;; GNode.builder() .type(\u0026#34;comment-node-parent\u0026#34;) .addCssClass(\u0026#34;comment-node-parent\u0026#34;) .size(720, 125) .position(point ?? Point.ORIGIN) .add( GNode.builder() .type(\u0026#34;comment-node\u0026#34;) .addArg(\u0026#34;text\u0026#34;, multiLineComment) .build() ) .build(); We create the custom node element as follows:\nexport class MultiLineTextNode extends GForeignObjectElement implements ArgsAware, EditableLabel { readonly isMultiLine = true; readonly args: Args; text = \u0026#34;\u0026#34;; override set bounds(bounds: Bounds) { /* ignore set bounds, always use the parent\u0026#39;s bounds */ } override get bounds(): Bounds { if (isBoundsAware(this.parent)) { return { x: this.position.x, y: this.position.y, width: this.parent.bounds.width, height: this.parent.bounds.height, }; } return Bounds.EMPTY; } // @ts-expect-error Arguments are set in the element override get code(): string { if (this.text === \u0026#34;\u0026#34;) { const textArg = this.args[\u0026#34;text\u0026#34;]; if (typeof textArg === \u0026#34;string\u0026#34;) { this.text = textArg; } } return `\u0026lt;pre\u0026gt;${this.text}\u0026lt;/pre\u0026gt;`; } override namespace = \u0026#34;http://www.w3.org/1999/xhtml\u0026#34;; get editControlDimension(): Dimension { return { width: this.bounds.width - 4, height: this.bounds.height - 4, }; } } To register this node type, we configure it with ForeignObjectView, disable moveFeature and selectFeature (as this handled by its parent node). To be able to edit this multi-line comment node we need to enable the editLabelFeature:\nconfigureModelElement( context, \u0026#34;comment-node-parent\u0026#34;, GNode, RoundedCornerNodeView ); configureModelElement( context, \u0026#34;comment-node\u0026#34;, MultiLineTextNode, ForeignObjectView, { disable: [moveFeature, selectFeature], enable: [editLabelFeature], } ); To style the parent node, we add this simple CSS :\n.comment-node-parent .sprotty-node { fill: lightgray; } The resulting diagram element is shown above and the corresponding HTML element code looks like this:\n\u0026lt;g transform=\u0026#34;scale(...) translate(...)\u0026#34;\u0026gt; \u0026lt;g id=\u0026#34;workflow-diagram_0_...\u0026#34; transform=\u0026#34;translate(...)\u0026#34; class=\u0026#34;comment-node-parent\u0026#34; \u0026gt; \u0026lt;rect x=\u0026#34;0\u0026#34; y=\u0026#34;0\u0026#34; width=\u0026#34;720\u0026#34; height=\u0026#34;125\u0026#34; class=\u0026#34;sprotty-node\u0026#34;\u0026gt;\u0026lt;/rect\u0026gt; \u0026lt;g class=\u0026#34;comment-node\u0026#34; id=\u0026#34;workflow-diagram_0_...\u0026#34;\u0026gt; \u0026lt;foreignObject requiredFeatures=\u0026#34;http://www.w3.org/TR/SVG11/feature#Extensibility\u0026#34; height=\u0026#34;125\u0026#34; width=\u0026#34;720\u0026#34; x=\u0026#34;0\u0026#34; y=\u0026#34;0\u0026#34; \u0026gt; \u0026lt;pre\u0026gt; Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam at tellus quis lacus auctor congue vel sit amet lectus. Cras interdum lectus vel enim mollis maximus. \u0026lt;/pre\u0026gt; \u0026lt;/foreignObject\u0026gt; \u0026lt;/g\u0026gt; \u0026lt;/g\u0026gt; \u0026lt;/g\u0026gt; PreRenderedView The PreRenderedView visualizes a previously rendered piece of svg code as a separate SVG element. This enables putting SVG code directly in the graphical model, which may be useful for including complex images for certain use cases. However, usually it is recommended to create a dedicated element type and register a dedicated view, which produces custom SVG, as this yields more flexibility to take bounds, etc., into account.\nThe implementation here is rather similar to the GForeignObjectElement, is a sub-class of GShapedPreRenderedElement. Please see GForeignObjectElement\u0026rsquo;s example implementation.\nRectangularNodeView A RectangularNodeView creates a rectangular shape based on the shape element\u0026rsquo;s size.\nRectangular nodes with dimensions of `(25,25)` (1), `(35,15)` (2) and `(5,40)`. Example implementation Java GLSP Server new GNodeBuilder() .type(DefaultTypes.NODE_RECTANGLE) .position(point.orElse(GraphUtil.point(0, 0))) .size(GraphUtil.dimension(25, 25)) .build(); Node GLSP Server GNode.builder() .type(DefaultTypes.NODE_RECTANGLE) .position(point ?? Point.ORIGIN) .size(25, 25) .build(); The rectangular node element and its view are configured as follows:\nconfigureModelElement( context, DefaultTypes.NODE_RECTANGLE, RectangularNode, RectangularNodeView ); GGraphView The GGraphView renders the base SVG canvas for a GModel and triggers the rendering of its children.\nExample implementation Java GLSP Server new GGraphBuilder().build(); Node GLSP Server GGraph.builder().build(); The graph element and its view are configured as follows:\nconfigureModelElement(context, DefaultTypes.GRAPH, GGraph, GGraphView); GLabelView The GLabelView renders a text element that contains the given label text.\nA label view with the text \"Label 1\" added to a rectangular node. Example implementation Java GLSP Server new GNodeBuilder() .type(DefaultTypes.NODE_RECTANGLE) .position(point.orElse(GraphUtil.point(0, 0))) .size(GraphUtil.dimension(50, 35)) .add(new GLabelBuilder().text(\u0026#34;Label 1\u0026#34;).build()) .build(); Node GLSP Server GNode.builder() .type(DefaultTypes.NODE_RECTANGLE) .position(point ?? Point.ORIGIN) .size(50, 35) .add(GLabel.builder().text(\u0026#34;Label 1\u0026#34;).build()) .build(); The label element and its view are configured as follows:\nconfigureModelElement(context, DefaultTypes.LABEL, GLabel, GLabelView); GRoutingHandleView A GRoutingHandleView renders a circle shaped element that serves as routing point for routable elements (e.g. Edges). Its position is computed either by a registered EdgeRouterRegistry or the routing arguments of the element.\nA manually added routing point (dark gray), at position `(0,100)`. Example implementation Java GLSP Server new GEdgeBuilder() .source(source) // source node element .target(target) // target node element .addRoutingPoint(GraphUtil.point(0, 100)) .build(); Node GLSP Server GEdge.builder() .source(source) // source node element .target(target) // target node element .addRoutingPoint(0, 100) .build(); The routing handle element and its view are configured as follows:\nconfigureModelElement( context, DefaultTypes.ROUTING_POINT, GRoutingHandle, GRoutingHandleView ); Default GLSP Views The following views are provided by the GLSP client framework.\nGEdgeView A GEdgeView renders a line element which is routed by the EdgeRouterRegistry. The view also triggers the rendering of additional elements (such as mouse handles) and edge children (such as edge labels or routing points).\nA GEdge connection two nodes. Example implementation Java GLSP Server new GEdgeBuilder() .source(source) // source node element .target(target) // target node element .build(); Node GLSP Server GEdge.builder() .source(source) // source node element .target(target) // target node element .build(); The edge element and its view are configured as follows:\nconfigureModelElement(context, DefaultTypes.EDGE, GEdge, GEdgeView); GIssueMarkerView A GIssueMarkerView renders an issue marker on top of shapes. This is used to show validation results on elements (see Model Validation). These issue markers are elements in the shape of an information, warning or error icon based on the severity of the issue.\nA GIssue info marker placed at top left corner of a rectangular node. Example implementation Java GLSP Server new GNodeBuilder() .type(DefaultTypes.NODE_RECTANGLE) .position(point.orElse(GraphUtil.point(0, 0))) .size(GraphUtil.dimension(50, 35)) .add(new GIssueMarkerBuilder() .addIssue(new GIssueBuilder() .severity(GSeverity.INFO) .build()) .position(GraphUtil.point(-8, -8)) .build()); Node GLSP Server GNode.builder() .type(DefaultTypes.NODE_RECTANGLE) .position(point ?? Point.ORIGIN) .size(50, 35) .add( GIssueMarker.builder() .addIssue({ message: \u0026#34;Information message\u0026#34;, severity: \u0026#34;info\u0026#34; }) .position(-8, -8) .build() ) .build(); The issue marker element and its view are configured as follows:\nconfigureModelElement( context, DefaultTypes.ISSUE_MARKER, GIssueMarker, GIssueMarkerView ); RoundedCornerNodeView A RoundedCornerNodeView creates a rectangular shape based shape\u0026rsquo;s size and computes and renders the corners in a rounded way, based on the corner radius argument. By default, the rounded corner radius defaults to 0.\nA rectangular node with corner radius `0` (1), `3` (2) and `15` (3). Example implementation Java GLSP Server new GNodeBuilder() .type(DefaultTypes.NODE) .position(point.orElse(GraphUtil.point(0, 0))) .size(GraphUtil.dimension(50, 35)) .addArguments(GArguments.cornerRadius(3)) .build(); Node GLSP Server GNode.builder() .type(DefaultTypes.NODE) .position(point ?? Point.ORIGIN) .size(50, 35) .addArgs(ArgsUtil.cornerRadius(3)) .build(); A node element and its rounded corner node view are configured as follows:\nconfigureModelElement(context, DefaultTypes.NODE, GNode, RoundedCornerNodeView); StructureCompartmentView The StructureCompartmentView allows to contain children if using the freeform Layout. For more details please see the section about freeform Layout.\n Styling The style of the rendered SVG elements is controlled with plain CSS. CSS classes can be declared directly in the corresponding view. The GLabelView, for instance, adds the CSS class ‘sprotty-label’ to the generated SVG text element.\nconst vnode = \u0026lt;text class-sprotty-label={true}\u0026gt;{label.text}\u0026lt;/text\u0026gt;; Graphical model elements also have a ‘cssClasses’ property which contains a list of CSS classes to be applied, in addition to the classes defined in the view. For instance, the server could send the following graphical model element:\n{ \u0026#34;id\u0026#34;: \u0026#34;myCustomLabel\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;label:custom\u0026#34;, \u0026#34;cssClasses\u0026#34;: [\u0026#34;my-custom-class\u0026#34;] } Keeping our previous model configuration in mind, the corresponding SVG element now has two css classes applied: ‘sprotty-label’ and ‘my-custom-class’.\nBased on those CSS classes, we can define CSS rules:\n.sprotty-label { fill: black; font-size: 100%; } .my-custom-class.sprotty-label { fill: red; } This simple style sheet declares that elements with the class ‘sprotty-label’’ should be rendered in black. If “my-custom-class’’ is applied as well they are rendered in red. To load this stylesheet it has to be imported somewhere in the project. Typically this is done in the \u0026ldquo;di.config.ts” file as it’s the entry point of the diagram DI container.\nimport \u0026#34;../css/diagram.css\u0026#34;; const customDiagramModule= new ContainerModule((bind,unbind, isBound,rebind)=\u0026gt;{ … }); ➡️ Now it\u0026rsquo;s best to learn more about client-side layouting next!\n","permalink":"https://www.eclipse.dev/glsp/documentation/rendering/","tags":null,"title":"Rendering \u0026 Styling"},{"categories":null,"contents":"We distinguish between micro and macro layout. Usually, the server is responsible for the macro layout, that is the arrangement of the main model elements (i.e. nodes and edges). This layout is defined already in the graphical model by means of coordinates. In turn, the client is responsible for the micro layout, that is the positioning and size computation of elements within a container element such as nodes. The client side (i.e. micro) layout can be configured in the graphical model, but will be applied on the client during the rendering phase. \nLayout Container Graphical elements that support client-side layouting of contained elements offer a layout property which defines the type of layouter that should be used. In addition, the behavior of the layouter can be configured with a set of layout options.\nFor an example let’s have a look at the following GNode:\n Java GLSP Server new GNodeBuilder() .layout(GConstants.Layout.VBOX) .layoutOptions(new GLayoutOptions() .hAlign(GConstants.HAlign.Center)) .add(new GLabelBuilder() .text(\u0026#34;label1\u0026#34;) .build()) .add(new GLabelBuilder() .text(\u0026#34;label2\u0026#34;) .build()) .build(); Node GLSP Server GNode.builder() .layout(\u0026#34;vbox\u0026#34;) .addLayoutOption(\u0026#34;hAlign\u0026#34;, \u0026#34;center\u0026#34;) .add(GLabel.builder().text(\u0026#34;label1\u0026#34;).build()) .add(GLabel.builder().text(\u0026#34;label2\u0026#34;).build()) .build(); This node contains two label elements that should be layouted vertically (from top to bottom) using the vbox layouter. In addition, we specify options for that layouter by defining that the children should be centered horizontally.\nLayout Options To adapt the chosen Layout (for more details please see below), the default Layout options AbstractLayoutOptions allow to configure:\n resizeContainer: boolean Indicates, if the container is resizable depending on its children (e.g. true for hbox layout) paddingTop: number Sets the height of the padding area on the top of an element to be positioned in pixel. paddingBottom: number Sets the height of the padding area on the bottom of an element to be positioned in pixel. paddingLeft: number Sets the height of the padding area to the left of an element to be positioned in pixel. paddingRight: number Sets the height of the padding area to the right of an element to be positioned in pixel. paddingFactor: number Defines the multiplication factor for an element\u0026rsquo;s size, which is then added as padding space around this element. E.g. For the stack layout 1 does not add additional padding to the element, 2 doubles the padding area around the element by its size. minWidth: number Defines the minimum width of an element to be positioned. minHeight: number Defines the minimum height of an element to be positioned. Usage Java GLSP Server To use the Layout options on the GLSP Java Server, there are several utility classes for option keys and values, for example:\n Edge label placement GConstants.EdgeSide: LEFT | RIGHT | TOP | BOTTOM | ON Horizontal alignment GConstants.HAlign: LEFT | CENTER | RIGHT For more details, please see GLayoutOptions and GConstants.\n Usage Node GLSP Server To use the Layout options on the GLSP Node Server, the support via utility classes for option keys and values is currently very limited, e.g. only the EdgeSide type is available. Therefore please use the dedicated string values in the meantime, e.g. \u0026quot;hAlign\u0026quot; \u0026quot;center\u0026quot; and so on.\n Default GLSP Layouters In general, layouters can be applied to elements that are compartments, in order to layout the containers based on the sizes of their children.\nThere are three built-in layout types that can be used: hbox, vbox and freeform.\nhbox Layout The HBoxLayouterExt layouts children of a container in a horizontal (left to right) direction.\nThis layouter provides additional layout options via HBoxLayoutOptionsExt:\n hGap: number Defines a horizontal gap between elements in the same container in pixel. vAlign: VAlignment = \u0026lsquo;top\u0026rsquo; | \u0026lsquo;center\u0026rsquo; | \u0026lsquo;bottom\u0026rsquo; Defines the vertical alignment of the element to be positioned. hGrab: boolean Indicates whether the remaining horizontal size can be grabbed by its children. vGrab: boolean Indicates whether the remaining vertical size can be grabbed by its children. prefWidth: number | null Defines the preferred width of the container element, which will be assigned, if there is no manually specified width and the sizes of its children aren\u0026rsquo;t requiring a larger width to fit into the container. prefHeight: number | null Defines the preferred height of the container element, which will be assigned, if there is no manually specified height and the sizes of its children aren\u0026rsquo;t requiring a larger height to fit into the container. According to the layout options, the children of the container are layouted and as concluding step, the final bounds of the container are computed based on the sum of this children bounds.\nhbox Layout Example This example creates a compartment of the default type DefaultTypes.COMPARTMENT using the hbox layout. It adds two children, one icon compartment (with custom type \u0026quot;icon\u0026quot;) and one label. The layout options define a horizontal gap of 15 pixel between the children of the compartment.\n Java GLSP Server new GCompartmentBuilder() .type(DefaultTypes.COMPARTMENT) .layout(GConstants.Layout.HBOX) .layoutOptions(new GLayoutOptions().hGap(15)) .add(new GCompartmentBuilder() .type(\u0026#34;icon\u0026#34;) .build()) .add(new GLabelBuilder() .text(\u0026#34;label\u0026#34;) .build()) .build(); .build(); Node GLSP Server GCompartment.builder() .type(\u0026#34;comp\u0026#34;) .layout(\u0026#34;hbox\u0026#34;) .addLayoutOption(\u0026#34;hGap\u0026#34;, 15) .add(GCompartment.builder().type(\u0026#34;icon\u0026#34;).build()) .add(GLabel.builder().text(\u0026#34;label\u0026#34;).build()) .build(); On the client side, we need to configure (besides the default elements GCompartment and GLabel) a custom Icon element and a IconView like this: configureModelElement(context, 'icon', Icon, IconView). The Icon element definition and the IconView definition are taken from the workflow example.\nThe resulting element with the obvious horizontal gap between the child elements is shown in the following image:\nUsing no `hGap` layout option (left) or `hGap` layout option with 15 pixel (right) \nvbox Layout The VBoxLayouterExt layouts children of a container in a vertical (top to bottom) direction.\nThis layouter provides additional layout options via VBoxLayoutOptionsExt:\n vGap: number Defines a vertical gap between elements in the same container in pixel. hAlign: HAlignment = \u0026lsquo;left\u0026rsquo; | \u0026lsquo;center\u0026rsquo; | \u0026lsquo;right\u0026rsquo; Defines the horizontal alignment of the element to be positioned. hGrab: boolean Indicates whether the remaining horizontal size can be grabbed by its children. vGrab: boolean Indicates whether the remaining vertical size can be grabbed by its children. prefWidth: number | null Defines the preferred width of the container element, which will be assigned, if there is no manually specified width and the sizes of its children aren\u0026rsquo;t requiring a larger width to fit into the container. prefHeight: number | null Defines the preferred height of the container element, which will be assigned, if there is no manually specified height and the sizes of its children aren\u0026rsquo;t requiring a larger height to fit into the container. According to the layout options, the children of the container are layouted and as concluding step, the final bounds of the container are computed based on the sum of this children bounds.\nvbox Layout Example This example creates a compartment of the default type DefaultTypes.COMPARTMENT using the vbox layout. It adds two children labels, which are positioned vertically from top to bottom and are centered horizontally.\n Java GLSP Server new GCompartmentBuilder() .type(DefaultTypes.COMPARTMENT) .layout(GConstants.Layout.VBOX) .layoutOptions(new GLayoutOptions().hAlign(GConstants.HAlign.CENTER)) .add(new GCompartmentBuilder() .type(\u0026#34;icon\u0026#34;) .build()) .add(new GLabelBuilder() .text(\u0026#34;Label\u0026#34;) .build()) .build(); .build(); Node GLSP Server GCompartment.builder() .type(\u0026#34;comp\u0026#34;) .layout(\u0026#34;vbox\u0026#34;) .addLayoutOption(\u0026#34;hAlign\u0026#34;, \u0026#34;center\u0026#34;) .add(GCompartment.builder().type(\u0026#34;icon\u0026#34;).build()) .add(GLabel.builder().text(\u0026#34;Label\u0026#34;).build()) .build(); On the client side, we use again the Icon element definition and IconView, please see section hbox Layout Example.\nThe resulting element positions its children vertically from top to bottom and aligns each element horizontally, this is shown in the following image:\nCenter children horizontally (left image) or align children to the left (right image) \nfreeform Layout The FreeFormLayouter positions the children of a container according to their explicit, parent-relative x/y coordinates inside the parent container. This layouter uses the default Layout Options and provides suitable default values (e.g. resizeContainer: true).\nAgain here, the children of the container are positioned according to their explicit positions inside the container and as concluding step, the final bounds of the container are computed based on the required bounds of its children.\nfreeform Layout Example This example creates a compartment of the custom type \u0026quot;comp:structure\u0026quot; using the freeform layout. It adds one child node, at the relative position (75, 35) of the parent container. The parent container defines its preferred size of 250 x 125.\n Java GLSP Server GDimension containerPrefSize = GraphUtil.dimension(250 /*width*/, 125 /*height*/); GPoint childPosition = GraphUtil.point(75 /*x*/, 35 /*y*/); Map\u0026lt;String, Object\u0026gt; layoutOptions = new HashMap\u0026lt;\u0026gt;(); layoutOptions.put(H_GRAB, true); layoutOptions.put(V_GRAB, true); layoutOptions.put(GLayoutOptions.KEY_PREF_WIDTH, containerPrefSize.getWidth()); layoutOptions.put(GLayoutOptions.KEY_PREF_HEIGHT, containerPrefSize.getHeight()); new GCompartmentBuilder() .type(\u0026#34;comp:structure\u0026#34;) .layout(GConstants.Layout.FREEFORM) .layoutOptions(layoutOptions) .add( new GNodeBuilder(DefaultTypes.NODE) .position(childPosition) .build() ) .build(); Node GLSP Server const containerPrefSize = { width: 250, height: 125 }; const childPosition = { x: 75, y: 35 }; const layoutOptions = { [\u0026#34;hGrab\u0026#34;]: true, [\u0026#34;vGrab\u0026#34;]: true, [\u0026#34;prefWidth\u0026#34;]: containerPrefSize.width, [\u0026#34;prefHeight\u0026#34;]: containerPrefSize.height, }; GCompartment.builder() .type(\u0026#34;comp:structure\u0026#34;) .layout(\u0026#34;freeform\u0026#34;) .addLayoutOptions(layoutOptions) .add(GNode.builder().type(\u0026#34;node\u0026#34;).position(childPosition).build()) .build(); On the client side, configure the \u0026quot;comp:structure\u0026quot; compartment as configureModelElement(context, 'struct', GCompartment, StructureCompartmentView).\nThe resulting compartment element positions its child at the desired position. The compartment defines its preferred size, which is used if the children do not enlarge the container. If the preferred size is omitted, the container\u0026rsquo;s size depends on its children.\nContainer with preferred size (left image) or without preferred size (right image) \nCustom Layouter Additional custom layouters can be contributed by creating a layouter class that extends the AbstractLayouter and optionally provide custom options that extend the AbstractLayoutOptions.\nIn the following example we show a simple custom layouter that extends the VBoxLayouterExt and provides a custom layout option, which increases the widths and heights of all children by a specified value, the default value is 50 pixel.\nGLSP Client export interface MyCustomLayoutOptions extends VBoxLayoutOptionsExt { enlargeSizeBy: number; } @injectable() export class MyCustomLayouter extends VBoxLayouterExt { static override KIND = \u0026#34;myCustomLayout\u0026#34;; protected override getChildrenSize( container: GParentElement \u0026amp; LayoutContainer, containerOptions: MyCustomLayoutOptions, layouter: StatefulLayouter ): Dimension { const result = super.getChildrenSize(container, containerOptions, layouter); return { width: result.width + containerOptions.enlargeSizeBy, height: result.height + containerOptions.enlargeSizeBy, }; } protected override getDefaultLayoutOptions(): MyCustomLayoutOptions { return { enlargeSizeBy: 50, ...super.getDefaultLayoutOptions(), }; } protected override spread( a: MyCustomLayoutOptions, b: MyCustomLayoutOptions ): MyCustomLayoutOptions { return { ...a, ...b }; } } Then the new custom layout needs to bound in the diagram module (di.config.ts):\nGLSP Client const myDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) =\u0026gt; { rebind(VBoxLayouter).to(MyCustomLayouter); } Custom layouter, which don\u0026rsquo;t overwrite existing ones, should extend the AbstractLayout and can be registered for a certain layouter key, e.g. MyCustomLayouter.KIND, that is used in the layout property of compartments as follows:\nGLSP Client const myDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) =\u0026gt; { configureLayout({ bind, isBound }, MyCustomLayouter.KIND, MyCustomLayouter); } With that, the custom layouter is ready to be used by compartments in the graphical model:\n Java GLSP Server new GCompartmentBuilder() .type(DefaultTypes.COMPARTMENT) .layout(\u0026#34;myCustomLayout\u0026#34;) .layoutOptions(Map.of(\u0026#34;enlargeSizeBy\u0026#34;, 15)) .add(new GLabelBuilder() .text(\u0026#34;label\u0026#34;) .build()) .build(); .build(); Node GLSP Server GCompartment.builder() .builder() .layout(\u0026#34;myCustomLayout\u0026#34;) .addLayoutOption(\u0026#34;enlargeSizeBy\u0026#34;, 15) .add(GLabel.builder().text(\u0026#34;label\u0026#34;).build()) .build(); Edge Layout Graphical elements that are typically rendered in combination with an edge such as labels have an edgeLayout property. This can be used to describe how the element should be aligned with the edge.\nFor example let\u0026rsquo;s have a look at the following GLabel:\n Java GLSP Server new GLabelBuilder() .edgePlacement(new GEdgePlacementBuilder() .side(GConstants.EdgeSide.TOP) .position(0.5) .build()) .add(new GLabelBuilder().text(\u0026#34;MyLabel\u0026#34;).build()) .build(); Node GLSP Server GLabel.builder() .edgePlacement({ side: \u0026#34;top\u0026#34;, position: 0.5, rotate: false, offset: 0 }) .add(new GLabelBuilder(GLabel).text(\u0026#34;MyLabel\u0026#34;).build()) .build(); The label above specifies the property edgePlacement. If this label is added as a child of an edge, it will be placed above the edge. The position is defined with 0.5 (i.e. 50 percent of the edge length) which means the label should be placed in the center of the edge. After the edge routes have been rendered the client will query all active edge placements and adjust the label position accordingly.\n","permalink":"https://www.eclipse.dev/glsp/documentation/clientlayouting/","tags":null,"title":"Client-side Layouting"},{"categories":null,"contents":"A UI Extension is a concept provided by the underlying framework Sprotty, which allows to display additional UI HTML elements on top of a diagram.\nIt is often used to add UI controls to the diagram. It is also frequently used in the core GLSP functionality:\n the GLSP tool palette the Sprotty command palette the Sprotty EditLabelUI Register and activate custom UI Extensions To implement a custom UI extension, it is necessary to extend the base AbstractUIExtension. This super class provides a base HTML element (containerElement) which is then the base for the custom UI elements. It also provides utility methods for showing/hiding or handling focus of the extension. To add the actual UI elements to the DOM, the abstract method initializeContents must be implemented.\nThe UIExtensionRegistry allows to register multiple UIExtensions per diagram.\nTo enable a UI Extension a SetUIExtensionVisibilityAction or SetUIExtensionVisibilityCommand with the respective UI Extension ID needs to be invoked.\nAs already mentioned, there is a broad use case for UI extensions, hence also their trigger events can of course vary. UI extensions can be enabled practically with any event that can dispatch an action, e.g. via context menu entries, mouse click events, keyboard events or changes of the diagram itself, for example the change of the diagram\u0026rsquo;s EditMode.\nFor example:\n The GLSP ToolPalette is opened via a IDiagramStartup hook right before the initial model is requested The CommandPalette is opened via the keyboard shortcut Ctrl+Space. The EditLabelUI is opened via a double click on an editable Label (e.g. a Category or Task node in the Workflow example). The TaskEditor in the Workflow example is opened via a context menu entry (Direct Edit Task) for the Task node. The following GIF shows the different triggers of the the mentioned extensions:\nButton Overlay Showcase The following section gives an overview of the necessary bits to create a very simple UI Extension that provides two buttons to center a diagram or fit it to the screen. This showcase is implemented on top of the Workflow example in the glsp-client and glsp-theia-integration.\nAt first, the UI extension needs to be defined, in this case we want the provided base HTML element (containerElement) to contain our buttons. Those buttons should show an icon as well as a descriptive text label and should trigger the respective GLSP Action on click of either one of them. Therefore we create for each button a \u0026lt;div\u0026gt; which contains the icon \u0026lt;i\u0026gt; and the description as adjacent text. The Button overlay should become visible after the initial model has been loaded. Therefore we implement the IDiagramStartup interface and trigger the show() method in the postModelInitialization hook.\n@injectable() export class ButtonOverlay extends AbstractUIExtension implements IDiagramStartup { @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; static readonly ID = \u0026#39;button-overlay\u0026#39;; id() { return ButtonOverlay.ID; } containerClass() { return ButtonOverlay.ID; } protected initializeContents(containerElement: HTMLElement): void { containerElement.appendChild(this.createButton(\u0026#39;btn_center_diagram\u0026#39;, \u0026#39;Center\u0026#39;, \u0026#39;screen-normal\u0026#39;, CenterAction.create([]))); containerElement.appendChild(this.createButton(\u0026#39;btn_fit_diagram\u0026#39;, \u0026#39;Fit to screen\u0026#39;, \u0026#39;screen-full\u0026#39;, FitToScreenAction.create([]))); } protected createButton(id: string, label: string, codiconId: string, action: Action): HTMLElement { const baseDiv = document.getElementById(this.options.baseDiv); if (baseDiv) { const button = document.createElement(\u0026#39;div\u0026#39;); const insertedDiv = baseDiv.insertBefore(button, baseDiv.firstChild); button.id = id; button.classList.add(\u0026#39;overlay-button\u0026#39;); const icon = this.createIcon(codiconId); insertedDiv.appendChild(icon); insertedDiv.onclick = () =\u0026gt; this.actionDispatcher.dispatch(action); insertedDiv.insertAdjacentText(\u0026#39;beforeend\u0026#39;, label); return button; } return document.createElement(\u0026#39;div\u0026#39;); } protected createIcon(codiconId: string): HTMLElement { const icon = document.createElement(\u0026#39;i\u0026#39;); icon.classList.add(...codiconCSSClasses(codiconId), \u0026#39;overlay-icon\u0026#39;); return icon; } postModelInitialization(): MaybePromise\u0026lt;void\u0026gt; { this.show(this.editorContext.modelRoot); } } To improve the styling of the extension, we make use of the defined CSS classes and position the overlay on the top left of the diagram and reduce the opacity of the buttons by default. On hover, the buttons are fully visible.\n.button-overlay { position: absolute; left: 25px; top: 25px; text-align: left; min-width: 150px; display: flex; flex-direction: column; z-index: 1000; font-family: sans-serif; } .overlay-button { opacity: 0.5; display: flex; } .overlay-button:hover { opacity: 1; cursor: pointer; } .overlay-icon { margin-right: 10px; padding-left: 5px; width: 16px; height: 16px; text-align: center; } To register the extension, it has to be bound as singleton and\nbind(ButtonOverlay).toSelf().inSingletonScope(); bind(TYPES.IUIExtension).toService(ButtonOverlay); bind(TYPES.IDiagramStartup).toService(ButtonOverlay); The outcome of this showcase is the following subtle UI overlay that offers to center the diagram or fit it to the screen:\n","permalink":"https://www.eclipse.dev/glsp/documentation/ui-extensions/","tags":null,"title":"UI Extensions"},{"categories":null,"contents":"Overview The client and the server communicate bidirectionally by sending actions via JSON-RPC. In addition, they are also used for the internal event flow in both the GLSP server and the GLSP client. Any service, mouse tool, etc. can issue actions by invoking the action dispatcher, either on the client or the server.\nThe action dispatcher – there is one on the client and one on the server – is the central component responsible for dispatching actions to their designated action handlers.\nWhen the dispatcher receives a new action for dispatching, it determines whether it should be dispatched to the internal action handlers only or submitted to the opposite component via JSON-RCP (server or client), based on the registered handlers on the server or the client.\nThe dispatcher distinguishes between notifications and request-response action pairs. Notification actions are one-way actions transferred between client and server. This means when the action dispatcher dispatches a notification it does not wait for a response and directly continues with dispatching the next incoming action. Request actions are typically issued by the GLSP client and can be used to block client-side action dispatching until the server has sent a corresponding response action.\nGLSP defines the standard action types of the graphical language server protocol. However, adopters can add new custom action types. Besides, adopters can replace and extend existing, or add additional action handlers for standard or custom action types.\nTo do that the following steps have to be performed:\n Create a new action specification by providing a corresponding Action implementation Create a new action handler for the newly created action type by providing a implementation of the ActionHandler interface Configure the new action type and handler in the DI module. Action specification Adopters can declare new custom actions by providing an implementation for the Action interface (resp. base class).\nJava GLSP Server public class MyCustomAction extends Action { public static final String KIND= \u0026#34;myCustomKind\u0026#34;; private String additionalInformation; public MyCustomAction() { super(KIND); } public String getAdditionalInformation() { return additionalInformation; } public void setAdditionalInformation(final String additionalInformation) { this.additionalInformation = additionalInformation; } } GLSP Client/Node GLSP Server export interface MyCustomAction extends Action { kind: typeof MyCustomAction.KIND; additionalInformation: string; } export namespace MyCustomAction { export const KIND = \u0026#39;myCustomKind\u0026#39;; export function is(object: any): object is MyCustomAction { return (Action.hasKind(object, KIND) \u0026amp;\u0026amp; hasStringProp(object, \u0026#39;additionalInformation\u0026#39;)); } export function create(options: { additionalInformation?: string }): MyCustomAction { return { kind: KIND, ...options }; } } Each action specification has a unique “kind” and can optionally declare additional data properties. We recommend defining the action kind as a static constant of the implementing class so that it can be accessed from other places, e.g. when registering the handler. Note that action instances need to be serializable to JSON. Therefore the class should only contain plain data properties and no additional business logic. In addition, references to graphical model elements should be done by id.\nIf an action is interchanged between client and server both need to provide the corresponding action definition.\nRequest-Response Actions If the client should be able to dispatch the new action as a blocking request, the action specification class has to implement or extend RequestAction.\nJava GLSP Server public class MyCustomRequestAction extends RequestAction\u0026lt;MyCustomResponseAction\u0026gt; { public static final String KIND= \u0026#34;myCustomRequest\u0026#34;; private String additionalInformation; public MyCustomRequestAction() { super(\u0026#34;my.custom.kind\u0026#34;); } public String getAdditionalInformation() { return additionalInformation; } public void setAdditionalInformation(final String additionalInformation) { this.additionalInformation = additionalInformation; } } GLSP Client/Node GLSP Server export interface MyCustomAction extends RequestAction\u0026lt;MyCustomResponseAction\u0026gt; { kind: typeof MyCustomAction.KIND; additionalInformation: string; } export namespace MyCustomAction { export const KIND = \u0026#39;myCustomKind\u0026#39;; export function is(object: any): object is MyCustomAction { return (RequestAction.hasKind(object, KIND) \u0026amp;\u0026amp; hasStringProp(object, \u0026#39;additionalInformation\u0026#39;)); } export function create(options: { additionalInformation?: string, requestId?: string }): MyCustomAction { return { kind: KIND, requestId: \u0026#39;\u0026#39;, ...options }; } } Each request action has a “requestId” and defines its response action as a type parameter. Of course, the response action specification has to be specified as well:\nJava GLSP Server public class MyCustomResponseAction extends ResponseAction { public static final String KIND = \u0026#34;myCustomResponse\u0026#34;; public MyCustomResponseAction() { super(KIND); } } GLSP Client/Node GLSP Server export interface MyCustomResponseAction extends ResponseAction { kind: typeof MyCustomResponseAction.KIND; } export namespace MyCustomResponseAction { export const KIND = \u0026#39;myCustomResponse\u0026#39;; export function is(object: any): object is SetContextActions { return Action.hasKind(object, KIND); } export function create(options: { responseId?: string } = {}): SetContextActions { return { kind: KIND, responseId: \u0026#39;\u0026#39;, ...options }; } } The client can dispatch a request action either in blocking fashion awaiting the response:\n@inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; … const response = await this.actionDispatcher.request(MyCustomRequestAction.create({ additionalInformation: \u0026#34;info\u0026#34; })); // response is of type MyCustomResponseAction or simply dispatch the action as non-blocking notification:\n@inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; … this.actionDispatcher.dispatch(MyCustomRequestAction.create({ additionalInformation: \u0026#34;info\u0026#34; })); Response actions don’t necessarily have to be part of a response-request action pair and can also be dispatched without a preceding request action. \nImplementing an Action Handler (GLSP Server) To create a new action handler, a class that implements the ActionHandler interface has to be created. In general, an action handler can handle one or more action kinds. However, handling multiple action kinds is typically reserved for rather uncommon edge cases. Therefore, the Java GLSP server provides an abstract base class that is designed for the single-action-kind-per-handler use case.\nJava GLSP Server public class MyCustomActionHandler extends AbstractActionHandler\u0026lt;MyCustomResponseAction\u0026gt; { @Override protected List\u0026lt;Action\u0026gt; executeAction(final MyCustomResponseAction actualAction) { // implement your custom logic to handle the action // Finally issue response actions // If no response actions should be issued \u0026#39;none()\u0026#39; can be used; return listOf(new MyCustomResponseAction()); } } Node GLSP Server @injectable() export class MyCustomActionHandler implements ActionHandler { actionKinds = [MyCustomRequestAction.KIND]; execute(action: MyCustomRequestAction): MaybePromise\u0026lt;Action[]\u0026gt; { // implement your custom logic to handle the action // Finally issue response actions // If no response actions should be issued \u0026#39;[]\u0026#39; can be used; return [new MyCustomResponseAction()]; } } The executeAction() method has to be implemented to provide the custom logic of your action handler. It returns a set of response actions that should be dispatched after the handler execution.\nNext, the custom handler has to be configured in the DiagramModule:\nJava GLSP Server @Override protected void configureActionHandlers(final MultiBinding\u0026lt;ActionHandler\u0026gt; binding) { super.configureActionHandlers(binding); binding.add(MyCustomActionHandler.class); } Node GLSP Server protected override configureActionHandlers(binding: InstanceMultiBinding\u0026lt;ActionHandlerConstructor\u0026gt;): void { super.configureActionHandlers(binding); binding.add(MyCustomActionHandler); } Request-Response Handling Action handlers can treat request-response actions in the same way as plain actions. No special handling is required. The action dispatcher tracks all incoming request actions and automatically intercepts the corresponding response action to set the correct response id. \nImplementing an Action Handler (GLSP Client) On the client, GLSP reuses the IActionHandler API of Sprotty. Therefore, to create a new action handler, a class that implements the IActionHandler interface has to be created.\n@injectable() export class MyCustomResponseActionHandler implements IActionHandler { handle(action: MyCustomResponseAction): void | Action { // implement your custom logic to handle the action // Optionally issue a response action } } The handle() method has to be implemented to provide the custom logic of your action handler. It optionally returns a response action that should be dispatched after the handler execution.\nA dedicated configuration function is available to configure the new action handler in the diagram module (“di.config.ts”):\nconst diagramModule = new ContainerModule((bind, _unbind, isBound, rebind) =\u0026gt; { const context = { bind, _unbind, isBound, rebind }; configureActionHandler(context, MyCustomResponseAction.KIND, MyCustomResponseActionHandler); } The configureActionHandler() function takes the inversify binding context, the action kind that should be handled, and the action handler class, as input. It registers the action handler for the given action kind, so that it can be retrieved by the action dispatcher.\n","permalink":"https://www.eclipse.dev/glsp/documentation/actionhandler/","tags":null,"title":"Actions \u0026 Action Handlers"},{"categories":null,"contents":"Operations are just special actions Remark: This documentation is outdated and not updated for GLSP 2.0 yet!\n In the previous section, we discussed actions and action handlers as the general way of how a diagram client and a diagram server communicate with each other and how they can invoke behavior or query data from each other. If such a behavior of an action now is intended to change the underlying source model on the server, there is a dedicated type of action for that: a model operation or operation for short.\nAn operation is a special type of action denoting a request for performing a specific modification of the source model. An operation kind identifier declares the specific modification to be applied. GLSP defines a set of reusable pre-defined operation types, such as for adding and deleting nodes and edges (see protocol for a detailed list). This pre-defined set of operations, however, can be easily extended by custom operation types. Each operation type can define additional attributes, such as the coordinates on which a node is to be created, that are required to perform the model modification. Note, however, that many pre-defined operation types support generic arguments (see property args in the type definitions), which allow to pass on simple, custom data with the existing pre-defined operations without the overhead of defining custom operation types.\nIn the following, we show an example of defining a custom operation.\nGLSP Client/Node GLSP Server export interface MyOperation extends Operation { kind: MyOperation.KIND; elementId: string; location?: Point; } export namespace MyOperation { export const KIND = \u0026#39;runMyOperation\u0026#39;; export function create( elementId: string, options: { location?: Point } = {} ): MyOperation { return { kind: KIND, isOperation: true, elementId, ...options }; } } Java GLSP Server public class MyOperation extends Operation { private String elementId; private GPoint location; public MyOperation(final String elementId) { this(elementId, null); } public MyOperation(final String elementId, final GPoint location) { this(); this.elementId = elementId; this.location = location; } public MyOperation() { super(\u0026#34;runMyOperation\u0026#34;); } public String getElementId() { return elementId; } public void setElementId(final String elementId) { this.elementId = elementId; } public Optional\u0026lt;GPoint\u0026gt; getLocation() { return Optional.ofNullable(location); } public void setLocation(final GPoint location) { this.location = location; } } As an operation is just a special type of action, it can be invoked as any other action via the action dispatcher.\n@inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; … this.actionDispatcher.dispatch(MyOperation.create(element.id, { location: { ... } })); Operation handlers vs. operation action handler Only the server is capable of performing source model modifications and, thus, operations must always be handled on the server only. Therefore, there is a single action handler on the server that handles all types of operations: the OperationActionHandler. This single operation action handler, however, delegates the execution of the actual source model modification to one of the specific OperationHandler implementations registered for specific operation types on the server.\nIn summary, the operation action handler manages the steps to be done before and after any model manipulation, whereas the actual source model manipulations are implemented in operation handlers that can be registered for specific operation types.\nOperation handlers Operation handlers are implementations of a specific type of source model manipluations, such as adding or deleting nodes. Thus, they are registered for a specific operation kind in the diagram server modules. Of course, operation handlers are typically specific to the respective source model language and framework (i.e. the data format, metamodel, or model API), as they implement how the current source model needs to be changed for a specific operation kind. Consequently, the actual implementation is specific to your particular scenario.\nThe registration and basic structure of operation handlers is set up as follows:\nJava GLSP Server public class MyServerModule extends DiagramModule { ... @Override protected void configureOperationHandlers(final MultiBinding\u0026lt;OperationHandler\u0026gt; binding) { binding.add(MyOperationHandler.class); } } public class MyOperationHandler extends AbstractOperationHandler\u0026lt;MyOperation\u0026gt; { @Inject protected MyModelState modelState; @Override protected void executeOperation(final MyOperation operation) { // modify your model state here } } Node GLSP Server @injectable() export class MyServerModule extends DiagramModule { ... configureOperationHandlers(binding: InstanceMultiBinding\u0026lt;OperationHandlerConstructor\u0026gt;): void { binding.add(MyOperationHandler); } } @injectable() export class MyOperationHandler implements OperationHandler { readonly operationType = MyOperation.KIND; @inject(MyModelState) protected modelState: MyModelState; execute(operation: MyOperation): MaybePromise\u0026lt;void\u0026gt; { // modify your model state here } } Operation action handler The operation action handler manages the steps to be done before and after any model manipulation. Usually, the operation action handler typically runs the operation handler\u0026rsquo;s modification within the scope of a command stack, to support undo and redo and handles all other aspects that need to be taken care of for any type of modification. For instance, after each successful source model modification done by an OperationHandler, the OperationActionHandler triggers the regeneration of the graphical model by calling the GModelFactory and sends the updated graph model back to the client using an UpdateModelAction, which then renders the update of the diagram. See also the process of editing the source model.\nAlso, the operation action handler is a good place in general to introduce any other steps that need to be performed before or after each model manipulation, such as triggering live validation, sending notifications to external services, etc. Thus, it isn\u0026rsquo;t unusual to overwrite the operation action handler for specific scenarios to inject before/after modification steps.\nThe operation action handler is typically agnostic of the actual source model language; it usually is, however, specific to the source model framework (e.g. EMF, JSON, etc.) as it needs to encapsulate the modification in framework-specific command API to support native undo/redo, or take snapshots before and after the manipulation and compute a patch, etc. For the available source model framework integration of GLSP (EMF, GModel-only, and JSON), a dedicated operation action handler, which often also entails a specific interface for the operation handler implementations, is provided by the respective GLSP source model libraries.\nThe respective default implementation of the operation action handler can be overridden in the diagram module by rebinding the implementation bound to the OperationActionHandler interface.\n","permalink":"https://www.eclipse.dev/glsp/documentation/modeloperations/","tags":null,"title":"Model Operations"},{"categories":null,"contents":"Validation rules for the source and/or graphical model are typically implemented on the GLSP server, as only the server has the full knowledge of the underlying model. Model validation rules are by nature specific to the underlying modeling language and therefore have to be provided by the specific server implementation. The interface to be implemented is called ModelValidator. The ModelValidator is invoked for a set of model elements and creates issue markers for them.\nIn many scenarios, the ModelValidator implementation delegates to another component, such as EMF validation, etc.\nLet\u0026rsquo;s have a look at a simple example implementation of a model validator:\nJava Server public class CustomModelValidator implements ModelValidator { @Override public List\u0026lt;Marker\u0026gt; validate(final GModelElement... elements) { List\u0026lt;Marker\u0026gt; markers = new ArrayList\u0026lt;\u0026gt;(); for (GModelElement element : elements) { if (element instanceof GNode) { markers.add(validateGNode((GNode) element)); } element.getChildren().forEach(child -\u0026gt; markers.addAll(validate(child))); } return markers; } protected Marker validateGNode(final GNode element) { return new Marker(\u0026#34;Node\u0026#34;, \u0026#34;This graphical element is a node\u0026#34;, element.getId(), MarkerKind.INFO); } } Node Server @injectable() export class CustomModelValidator implements ModelValidator { @inject(ModelState) protected readonly modelState: ModelState; validate(elements: GModelElement[]): Marker[] { const markers: Marker[] = []; for (const element of elements) { if (element instanceof GNode) { markers.push(this.validateGNode(element)); } if (element.children) { markers.push(...this.validate(element.children)); } } return markers; } protected validateGNode(element: GNode): Marker { return { kind: MarkerKind.INFO, description: \u0026#39;This graphical element is a node\u0026#39;, elementId: element.id, label: \u0026#39;Node\u0026#39; }; } } This validator iterates over the given model elements and their children and creates a new info marker for each element that is an instance of GNode. An issue marker is associated with a graphical element via its “id”. In addition, the marker has a label, an issue description and a kind indicating its severity. The severity can either be “INFO”,”WARN” or “ERROR”. The created markers are then propagated to the client via a SetMarkersAction.\nTo enable the custom model validator, it has to be bound in the diagram module.\nJava Server @Override protected Class\u0026lt;? extends ModelValidator\u0026gt; bindModelValidator() { return CustomModelValidator.class; } Node Server protected override bindModelValidator(): BindingTarget\u0026lt;ModelValidator\u0026gt; | undefined { return CustomModelValidator; } Validation is typically triggered by the client via a RequestMarkersAction – e.g., by clicking the validation button in the tool palette. In addition, the server can also send a SetMarkersAction to the client without a preceding request at any time. This is useful to implement features such as automatic live validation after each model update.\nThe client visualizes each marker by decorating the corresponding graphical element with an icon indicating the severity. When hovering over the issue marker a tooltip with the issue description is displayed.\nDepending on the chosen platform integration, validation markers are propagated to other user interface components. For instance, when integrating GLSP into Theia, markers are also translated into items shown in the Problems view.\n","permalink":"https://www.eclipse.dev/glsp/documentation/validation/","tags":null,"title":"Model Validation"},{"categories":null,"contents":"Following the successful pattern provided by the language server protocol (LSP) for textual languages, GLSP provides a defined protocol for the communication between the GLSP client and the server. In this modular architecture, server and client are well encapsulated and can be developed in an independent way. This allows mixing in the ideal technologies on both sides and reusing existing clients and server.\n1. Server-Client Lifecycle The base communication between the client and server is performed using action messages whereas we assume that each client connection will start their own server instance. Thus each server is only responsible for a single client.\nA client implementation must consider the following interface:\nCode interface GLSPClient { /** * Unique client Id. */ readonly id: string; /** * Current client state. */ readonly currentState: ClientState; /** * Initializes the client and the server connection. During the start procedure the client is in the * `Starting` state and will transition to either `Running` or `StartFailed`. Calling this method * if the client is already running has no effect. * * @returns A promise that resolves if the startup was successful. */ start(): Promise\u0026lt;void\u0026gt;; /** * Send an `initialize` request to the server. The server needs to be initialized in order to accept and * process other requests and notifications. The {@link InitializeResult} ist cached and can be retrieved * via the {@link GLSPClient.initializeResult} property. * Only the first method invocation actually sends a request to the server. Subsequent invocations simply * return the cached result. * * @param params Initialize parameters * @returns A promise of the {@link InitializeResult}. */ initializeServer(params: InitializeParameters): Promise\u0026lt;InitializeResult\u0026gt;; /** * The cached {@link {InitializeResult}. Is `undefined` if the server has not been initialized yet via * the {@link GLSPClient.initializeServer} method. */ readonly initializeResult: InitializeResult | undefined; /** * Event that is fired once the first invocation of {@link GLSPClient.initializeServer} has been completed. */ readonly onServerInitialized: Event\u0026lt;InitializeResult\u0026gt;; /** * Send an `initializeClientSession` request to the server. One client application may open several session. * Each individual diagram on the client side counts as one session and has to provide * a unique clientId. * * @param params InitializeClientSession parameters * @returns A promise that resolves if the initialization was successful */ initializeClientSession(params: InitializeClientSessionParameters): Promise\u0026lt;void\u0026gt;; /** * Sends a `disposeClientSession` request to the server. This request has to be sent at the end of client session lifecycle * e.g. when an editor widget is closed. * * @param params DisposeClientSession parameters * @returns A promise that resolves if the disposal was successful */ disposeClientSession(params: DisposeClientSessionParameters): Promise\u0026lt;void\u0026gt;; /** * Send a `shutdown` notification to the server. */ shutdownServer(): void; /** * Stops the client and disposes unknown resources. During the stop procedure the client is in the `Stopping` state and will * transition to either `Stopped` or `ServerError`. * * @returns A promise that resolves after the server was stopped and disposed. */ stop(): Promise\u0026lt;void\u0026gt;; /** * Send an action message to the server. * * @param message The message */ sendActionMessage(message: ActionMessage): void; /** * Sets a handler/listener for action messages received from the server. * Can be scoped to a particular client session by passing the corresponding `clientId`. * * @param handler The action message handler * @param clientId If passed given action message handler will only be invoked for action messages with this client id. * @returns A {@link Disposable} that can be used to unregister the handler */ onActionMessage(handler: ActionMessageHandler, clientId?: string): Disposable; } export enum ClientState { /** * The client has been created. */ Initial, /** * `Start` has been called on the client and the start process is still on-going. */ Starting, /** * The client failed to complete the start process. */ StartFailed, /** * The client was successfully started and is now running. */ Running, /** * `Stop` has been called on the client and the stop process is still on-going. */ Stopping, /** * The client stopped and disposed the server connection. Thus, action messages can no longer be sent. */ Stopped, /** * An error was encountered while connecting to the server. No action messages can be sent. */ ServerError } /** * A key-value pair structure for custom arguments. */ interface Args { [key: string]: string | number | boolean; } type ActionMessageHandler = (message: ActionMessage) =\u0026gt; void; In GLSP we provide a default client implementation based on JSON-RPC messages.\nInitialize Request\nThe initialize request has to be the first request from the client to the server. Until the server has responded with an InitializeResult no other request or notification can be handled and is expected to throw an error. A client is uniquely identified by an applicationId and has to specify on which protocolVersion it is based on. In addition, custom arguments can be provided in the args map to allow for custom initialization behavior on the server. The request returns an InitializeResult that encapsulates server information and capabilities. The InitializeResult is used inform the client about the action kinds that the server can handle for a specific diagramType.\nCode interface InitializeParameters { /** * Unique identifier for the current client application. */ applicationId: string; /** * GLSP protocol version that this client is implementing. */ protocolVersion: string; /** * Additional custom arguments e.g. application specific parameters. */ args?: Args; } interface InitializeResult { /** * GLSP protocol version that the server is implementing. */ protocolVersion: string; /** * The actions (grouped by `diagramType`) that the server can handle. */ serverActions: ServerActions; } /** * A key-value pair structure to map a `diagramType` to its server-handled action kinds. */ interface ServerActions { [key: string]: string[]; } InitializeClientSession Request\nWhen a new graphical representation (diagram) is created a InitializeClientSession request has to be sent to the server. Each individual diagram on the client side counts as one session and has to provide a unique clientSessionId and its diagramType. In addition, custom arguments can be provided in the args map to allow for custom initialization behavior on the server.\nCode interface InitializeClientSessionParameters { /** * Unique identifier for the new client session. */ clientSessionId: string; /** * Unique identifier of the diagram type for which the session should be configured. */ diagramType: string; /** * The set of action kinds that can be handled by the client. * Used by the server to know which dispatched actions should be forwarded to the client. */ clientActionKinds: string[]; /** * Additional custom arguments. */ args?: Args; } DisposeClientSession Request\nWhen a graphical representation (diagram) is no longer needed, e.g. the tab containing the diagram widget has been closed, a DisposeClientSession request has to be sent to the server. The session is identified by its unique clientSessionId. In addition, custom arguments can be provided in the args map to allow for custom dispose behavior on the server.\nCode interface DisposeClientSessionParameters { /** * Unique identifier of the client session that should be disposed. */ clientSessionId: string; /** * Additional custom arguments. */ args?: Args; } Shutdown Notification\nIf the client disconnects from the server, it may send a shutdown notification to give the server a chance to clean up any resources dedicated to the client. The shutdown request does not have any parameters as the server is already aware of the client.\nAction Messages\nAny communication that is performed between initialization and shutdown is handled by sending action messages, either from the client to the server or from the server to the client. This is the core part of the Graphical Language Server Protocol.\n2. Graphical Language Server Protocol The graphical language server protocol defines how the client and the server communicate and which actions are sent between them. It heavily builds on the client-server protocol defined in Sprotty but adds additional actions to enable editing and other capabilities. Additional information regarding the lifecycle of some action messages can be found in the Sprotty documentation.\nPlease note that there are several actions that are used purely on the client side. Such actions are not part of this protocol.\n2.1. Base Protocol The base protocol describes the structure of the messages that are sent between the server and the client.\n2.1.1. ActionMessage A general message serves as an envelope carrying an action to be transmitted between the client and the server via a DiagramServer.\nCode interface ActionMessage\u0026lt;A extends Action = Action\u0026gt; { /** * Used to identify a specific client session. */ clientId: string; /** * The action to execute. */ Action: A; } 2.1.2. Action An action is a declarative description of a behavior that shall be invoked by the receiver upon receipt of the action. It is a plain data structure, and as such transferable between server and client. Actions contained in action messages are identified by their kind attribute. This attribute is required for all actions. Certain actions are meant to be sent from the client to the server or vice versa, while other actions can be sent both ways, by the client or the server. All actions must extend the default action interface.\nCode interface Action { /** * Unique identifier specifying the kind of action to process. */ kind: string; } 2.1.2.1. RequestAction A request action is tied to the expectation of receiving a corresponding response action. The requestId property is used to match the received response with the original request.\nCode interface RequestAction\u0026lt;Res extends ResponseAction\u0026gt; extends Action { /** * Unique id for this request. In order to match a response to this request, the response needs to have the same id. */ requestId: string; } 2.1.2.2. ResponseAction A response action is sent to respond to a request action. The responseId must match the requestId of the preceding request. In case the responseId is empty or undefined, the action is handled as standalone, i.e. it was fired without a preceding request.\nCode interface ResponseAction extends Action { /** * Id corresponding to the request this action responds to. */ responseId: string; } 2.1.2.3. RejectAction A reject action is a response fired to indicate that a request must be rejected.\nCode interface RejectAction extends ResponseAction { kind: \u0026#39;rejectRequest\u0026#39;; /** * A human-readable description of the reject reason. Typically this is an error message * that has been thrown when handling the corresponding RequestAction. */ message: string; /** * Optional additional details. */ detail?: JsonAny; } 2.1.2.4. Operation Operations are actions that denote requests from the client to modify the model. Model modifications are always performed by the server. After a successful modification, the server sends the updated model back to the client using the UpdateModelAction.\nCode /** * Marker interface for operations. */ interface Operation extends Action { /** * Discriminator property to make operations distinguishable from plain Actions. */ isOperation: true; } /** * An operation that executes a list of operations. */ interface CompoundOperation extends Operation { readonly kind = \u0026#39;compound\u0026#39;; /** * List of operations that should be executed. */ operationList: Operation[]; } 2.2. Model Structure The basic model structure in GLSP is called a GModel. Such a model consists of GModelElements conforming to an GModelElementSchema.\nSprotty already defines a similar, graph-like model called SGraph conforming to the SGraphSchema. This graph consists nodes, edges, compartments, labels, and ports. The GModelis an extension of the sprotty graph model.\n2.2.1. GModelElementSchema The schema of an GModelElement describes its serializable form. The actual class-based model is created from its schema in a deserialization step on client and server side. Each model element must have a unique ID and a type that is used to look up its view, i.e., the graphical representation.\nCode interface GModelElementSchema { /** * Unique identifier for this element. */ id: string; /** * Type to look up the graphical representation of this element. */ type: string; /** * Children of this element. */ children?: GModelElementSchema[]; /** * CSS classes that should be applied on this element. */ cssClasses?: string[]; } 2.2.1.1. GModelRootSchema Serializable schema for the root element of the model tree. Usually actions refer to elements in the graphical model via an elementId. However, a few actions actually need to transfer the graphical model. In such cases, the graphical model needs to be represented as a serializable GModelRootSchema.\nCode interface GModelRootSchema extends GModelElementSchema { /** * Bounds of this element in the canvas. */ canvasBounds?: Bounds; /** * Version of this root element. */ revision?: number; } 2.2.2. GModelElement All elements of the diagram model inherit from base class GModelElement. Each model element must have a unique ID and a type that is used to look up its view. Additionally, each element provides access to its root element and holds an index to speed up the model element lookup.\nEach model element has a set of features. A feature is a symbol identifying some functionality that can be enabled or disabled for a model element, e.g. a resizeFeature. The set of supported features is determined by the features property.\nCode class GModelElement { /** * Unique identifier for this element. */ id: string; /** * Type to look up the graphical representation of this element. */ type: string; /** * CSS classes that should be applied on this element. */ cssClasses?: string[]; /** * A set of features supported by this element, e.g., */ features?: FeatureSet; /** * This element\u0026#39;s root element. */ root: GModelRoot; /** * Access to the model\u0026#39;s index for faster element lookup. */ index: GModelIndex\u0026lt;GModelElement\u0026gt;; } 2.2.2.1. GParentElement A parent element may contain child elements, thus the diagram model forms a tree.\nCode class GParentElement extends GModelElement { /** * Children of this element. */ readonly children: ReadonlyArray\u0026lt;GChildElement\u0026gt;; /** * Adds a child element to this element. */ add(child: GChildElement, index?: number); /** * Removes a child element from this element. */ remove(child: GChildElement); /** * Removes all child elements from this element. */ removeAll(filter?: (e: GChildElement) =\u0026gt; boolean); /** * Moves a child element to a new index. */ move(child: GChildElement, newIndex: number); } 2.2.2.2. GChildElement A child element is contained in a parent element. All elements except the model root are child elements. In order to keep the model class hierarchy simple, every child element is also a parent element, although for many elements the array of children is empty (i.e. they are leafs in the model element tree).\nCode class GChildElement extends GParentElement { /** * Parent of this element. */ readonly parent: GParentElement; } 2.2.2.3. GModelRoot Base class for the root element of the diagram model tree.\nCode class GModelRoot extends GParentElement { /** * Access to the index which is built up for faster element lookup. */ readonly index: GModelIndex\u0026lt;GModelElement\u0026gt;; /** * Bounds of this element in the canvas. */ canvasBounds?: Bounds; /** * Version of this root element. */ revision?: number; } 2.3. Types 2.3.1. Args Arguments are a key-value map with the key being a string and the value being either a string, a number, or a boolean value.\nCode type Args = { [key: string]: string | number | boolean }; 2.3.2. Point A Point is composed of the (x,y) coordinates of an object.\nCode interface Point { /** * The abscissa of the point. */ readonly x: number; /** * The ordinate of the point. */ readonly y: number; } 2.3.3. Dimension The Dimension of an object is composed of its width and height.\nCode interface Dimension { /** * The width of an element. */ readonly width: number; /** * the height of an element. */ readonly height: number; } 2.3.4. Bounds The bounds are the position (x, y) and dimension (width, height) of an object. As such the Bounds type extends both Point and Dimension.\nCode interface Bounds extends Point, Dimension {} 2.3.5. ElementAndBounds The ElementAndBounds type is used to associate new bounds with a model element, which is referenced via its id.\nCode interface ElementAndBounds { /** * The identifier of the element. */ elementId: string; /** * The new size of the element. */ newSize: Dimension; /** * The new position of the element. */ newPosition?: Point; } 2.3.6. ElementAndAlignment The ElementAndAlignment type is used to associate a new alignment with a model element, which is referenced via its id.\nCode interface ElementAndAlignment { /** * The identifier of an element. */ elementId: string; /** * The new alignment of the element. */ newAlignment: Point; } 2.3.7. ElementAndRoutingPoints The ElementAndRoutingPoints type is used to associate an edge with specific routing points.\nCode interface ElementAndRoutingPoints { /** * The identifier of an element. */ elementId: string; /** * The new list of routing points. */ newRoutingPoints?: Point[]; } 2.3.8. EditorContext The EditorContext may be used to represent the current state of the editor for particular actions. It encompasses the last recorded mouse position, the list of selected elements, and may contain custom arguments to encode additional state information.\nCode interface EditorContext { /** * The list of selected element identifiers. */ readonly selectedElementIds: string[]; /** * The last recorded mouse position. */ readonly lastMousePosition?: Point; /** * Custom arguments. */ readonly args?: Args; } 2.3.9. LabeledAction Labeled actions are used to denote a group of actions in a user-interface context, e.g., to define an entry in the command palette or in the context menu.\nCode interface LabeledAction { /** * Group label. */ label: string; /** * Actions in the group. */ actions: Action[]; /** * Optional group icon. */ icon?: string; } 2.4 Model Data 2.4.1. RequestModelAction Sent from the client to the server in order to request a graphical model. Usually this is the first message that is sent from the client to the server, so it is also used to initiate the communication. The response is a SetModelAction or an UpdateModelAction.\nCode interface RequestModelAction extends RequestAction\u0026lt;SetModelAction\u0026gt; { /** * The kind of the action. */ kind = \u0026#34;requestModel\u0026#34;; /** * Additional options used to compute the graphical model. */ options?: { [key: string]: string }); } 2.4.2. SetModelAction Sent from the server to the client in order to set the model. If a model is already present, it is replaced.\nCode interface SetModelAction extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;setModel\u0026#39;; /** * The new graphical model elements. */ newRoot: GModelRootSchema; } 2.4.3. UpdateModelAction Sent from the server to the client in order to update the model. If no model is present yet, this behaves the same as a SetModelAction. The transition from the old model to the new one can be animated.\nCode interface UpdateModelAction extends Action { /** * The kind of the action. */ kind = \u0026#39;updateModel\u0026#39;; /** * The new root element of the graphical model. */ newRoot?: GModelRootSchema; /** * Boolean flag to indicate wether updated/changed elements should be animated in the diagram. */ animate?: boolean; } 2.4.4. SourceModelChangedAction Sent from the server to the client in order to indicate that the source model has changed. The source model denotes the data source from which the diagram has been originally derived (such as a file, a database, etc.). Typically clients would react to such an action by asking the user whether she wants to reload the diagram or ignore the changes and continue editing. If the editor has no changes (i.e. is not dirty), clients may also choose to directly refresh the editor by sending a RequestModelAction.\nCode interface SourceModelChangedAction extends Action { /** * The kind of the action. */ kind = \u0026#39;sourceModelChanged\u0026#39;; /** * A human readable name of the source model (e.g. the file name). */ sourceModelName: string; } 2.5. Model Saving 2.5.1. SaveModelAction Sent from the client to the server in order to persist the current model state back to the source model. A new fileUri can be defined to save the model to a new destination different from its original source model.\nCode interface SaveModelAction extends Action { /** * The kind of the action. */ kind = \u0026#39;saveModel\u0026#39;; /** * The optional destination file uri. */ fileUri?: string; } 2.5.2. SetDirtyStateAction The server sends a SetDirtyStateAction to indicate to the client that the current model state on the server does not correspond to the persisted model state of the source model. A client may ignore such an action or use it to indicate to the user the dirty state.\nCode interface SetDirtyStateAction extends Action { /** * The kind of the action. */ kind = \u0026#39;setDirtyState\u0026#39;; /** * True if the current model state is dirty */ isDirty: boolean; /** * A string indicating the reason for the dirty state change e.g \u0026#39;operation\u0026#39;, \u0026#39;undo\u0026#39; ... */ reason?: string; } 2.5.3. RequestExportSvgAction A RequestExportSvgAction is sent by the client (or the server) to initiate the SVG export of the current diagram. The handler of this action is expected to retrieve the diagram SVG and should send an ExportSvgAction as response. Typically the ExportSvgAction is handled directly on client side.\nCode interface ExportSvgAction extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;exportSvg\u0026#39;; /** * The diagram GModel as serializable SVG. */ svg: string; /** * Id corresponding to the request this action responds to. */ responseId: string; } 2.5.4. ExportSvgAction The client sends an ExportSvgAction to indicate that the diagram, which represents the current model state, should be exported in SVG format. The action only provides the diagram SVG as plain string. The expected result of executing an ExportSvgAction is a new file in SVG-format on the underlying filesystem. However, other details like the target destination, concrete file name, file extension etc. are not specified in the protocol. So it is the responsibility of the action handler to process this information accordingly and export the result to the underlying filesystem.\nCode interface ExportSvgAction extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;exportSvg\u0026#39;; /** * The diagram GModel as serializable SVG. */ svg: string; /** * Id corresponding to the request this action responds to. */ responseId: string; } 2.6. Model Layout In GLSP the server usually controls the model\u0026rsquo;s layout by applying bounds to all elements and sending an updated model to the client (SetModelAction, UpdateModelAction). However, calculating the correct bounds of each element may not be straight-forward as it may depend on certain client-side rendering properties, such as label size.\nOn the client-side GLSP calculates the layout on two levels: The Micro Layout is responsible to layout a single element with all its labels, icons, compartments in a horizontal box, vertical box, or other layout containers. The Macro Layout is responsible for layouting the network of nodes and edges on the canvas. If a server needs information from the micro layout, it can send a RequestBoundsAction to the client who will respond with a ComputedBoundsAction containing all elements and their bounds.\n2.6.1. RequestBoundsAction Sent from the server to the client to request bounds for the given model. The model is rendered invisibly so the bounds can derived from the DOM. The response is a ComputedBoundsAction. This hidden rendering round-trip is necessary if the client is responsible for parts of the layout.\nCode interface RequestBoundsAction extends RequestAction { /** * The kind of the action. */ kind = \u0026#39;requestBounds\u0026#39;; /** * The model elements to consider to compute the new bounds. */ newRoot: GModelRootSchema; } 2.6.2. ComputedBoundsAction Sent from the client to the server to transmit the result of bounds computation as a response to a RequestBoundsAction. If the server is responsible for parts of the layout, it can do so after applying the computed bounds received with this action. Otherwise there is no need to send the computed bounds to the server, so they can be processed locally by the client.\nCode interface ComputedBoundsAction extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;computedBounds\u0026#39;; /** * The new bounds of the model elements. */ bounds: ElementAndBounds[]; /* * The revision number. */ revision?: number; /** * The new alignment of the model elements. */ alignments?: ElementAndAlignment[]; /** * The route of the model elements. */ routes?: ElementAndRoutingPoints[]; } 2.6.3. LayoutOperation Request a layout of the diagram or selected elements from the server.\nCode interface LayoutOperation extends Operation { /** * The kind of the action. */ kind = \u0026#39;layout\u0026#39;; /** * The identifiers of the elements that should be layouted, will default to the root element if not defined. */ elementIds?: string[]; } 2.7. Model Edit Mode GLSP supports setting the model into different edit modes. We pre-define two such modes: readonly and editable. However these modes can be customized as need be.\n2.7.1. SetEditModeAction Sent from the client to the server to set the model into a specific editor mode, allowing the server to react to certain requests differently depending on the mode. A client may also listen to this action to prevent certain user interactions preemptively.\nCode interface SetEditModeAction extends Action { /** * The kind of the action. */ kind = \u0026#39;setEditMode\u0026#39;; /** * The new edit mode of the diagram. */ editMode: string; } 2.8. Client-Side Actions There are several actions that are issued and processed on the client to manipulate the view port, select elements, etc. Those actions may also be sent by the server to trigger the respective client behavior. Please note that we only list actions here that are actually used by the current default implementation of the GLSP server.\n2.8.1. View Port View port actions manipulate the viewport on the client-side and may be sent from the server to highlight changes or improve general usability.\n2.8.1.1. CenterAction Centers the viewport on the elements with the given identifiers. It changes the scroll setting of the viewport accordingly and resets the zoom to its default. This action can also be created on the client but it can also be sent by the server in order to perform such a viewport change remotely.\nCode interface CenterAction extends Action { /** * The kind of the action. */ kind = \u0026#39;center\u0026#39;; /** * The identifier of the elements on which the viewport should be centered. */ elementIds: string[]; /** * Indicate if the modification of the viewport should be realized with or without support of animations. */ animate: boolean = true; /** * Indicates whether the zoom level should be kept. */ retainZoom: boolean = false; } 2.8.1.2. FitToScreenAction Triggers to fit all or a list of elements into the available drawing area. The resulting fit-to-screen command changes the zoom and scroll settings of the viewport so the model can be shown completely. This action can also be sent from the server to the client in order to perform such a viewport change programmatically.\nCode interface FitToScreenAction extends Action { /** * The kind of the action. */ kind = \u0026#39;fit\u0026#39;; /** * The identifier of the elements to fit on screen. */ elementIds: string[]; /** * The padding that should be visible on the viewport. */ padding?: number; /** * The max zoom level authorized. */ maxZoom?: number; /** * Indicate if the action should be performed with animation support or not. */ animate: boolean = true; } 2.8.2. Client Notification To notify the client about user-facing events or long running operations, the server can send a server status notification, a server message notification or progress notifications.\nStatus and message notifications are used to inform the user about one-time events. Status notifications are typically shown directly on the diagram as an overlay for a certain time. Status messages, on the other hand, are persistent notifications and are typically shown in a dedicated notification area until the user discards them.\nProgress notifications only appear while the long running operation is running and indicate the progress of this operation with messages and optionally a progress bar.\n2.8.2.1. StatusAction This action is typically sent by the server (or the client) to signal a state change. If a timeout is given the respective status should disappear after the timeout is reached.\nCode interface StatusAction extends Action { /** * The kind of the action. */ kind = \u0026#39;status\u0026#39;; /** * The severity of the status. */ severity: SeverityLeel; /** * The message describing the status. */ message: string; /** * Timeout after which a displayed status disappears. */ timeout?: number; } 2.8.2.2. MessageAction This action is typically sent by the server (or the client) to notify the user about something of interest.\nCode interface MessageAction extends Action { /** * The kind of the action. */ kind = \u0026#39;message\u0026#39;; /** * The severity of the message. */ severity: SeverityLevel; /** * The message text. */ message: string; /** * Further details on the message. */ details: string; } 2.8.2.3. SeverityLevel The severity of a status or message.\nCode /** * The possible server status severity levels. */ type SeverityLevel = \u0026#39;NONE\u0026#39; | \u0026#39;INFO\u0026#39; | \u0026#39;WARNING\u0026#39; | \u0026#39;ERROR\u0026#39; | \u0026#39;FATAL\u0026#39; | \u0026#39;OK\u0026#39;; 2.8.2.4. StartProgressAction This action is sent by the server to the client to request presenting the progress of a long running process in the UI.\nCode export interface StartProgressAction extends Action { /** * The kind of the action. */ kind = \u0026#39;startProgress\u0026#39;; /** * An ID that can be used in subsequent `updateProgress` and `endProgress` events to make them refer to the same progress reporting. */ progressId: string; /** * Short title of the progress reporting. Shown in the UI to describe the long running process. */ title: string; /** * Optional additional progress message. Shown in the UI to describe the long running process. */ message?: string; /** * Progress percentage to display (value range: 0 to 100). If omitted no percentage is shown. */ percentage?: number; } 2.8.2.5. UpdateProgressAction This action is sent by the server to the client to presenting an update of the progress of a long running process in the UI.\nCode export interface UpdateProgressAction extends Action { /** * The kind of the action. */ kind = \u0026#39;updateProgress\u0026#39;; /** * The ID of the progress reporting to update. */ progressId: string; /** * The message to show in the progress reporting. */ message?: string; /** * The percentage (value range: 0 to 100) to show in the progress reporting. */ percentage?: number; } 2.8.2.6. EndProgressAction This action is sent by the server to the client to end the reporting of a progress.\nCode export interface EndProgressAction extends Action { /** * The kind of the action. */ kind = \u0026#39;endProgress\u0026#39;; /** * The ID of the progress reporting to update. */ progressId: string; /** * The message to show in the progress reporting. */ message?: string; } 2.8.3. Element Selection 2.8.3.1. SelectAction Triggered when the user changes the selection, e.g. by clicking on a selectable element. The action should trigger a change in the selected state accordingly, so the elements can be rendered differently. The server can send such an action to the client in order to change the selection remotely.\nCode interface SelectAction extends Action { /** * The kind of the action. */ kind = \u0026#39;elementSelected\u0026#39;; /** * The identifier of the elements to mark as selected. */ selectedElementsIDs: string[]; /** * The identifier of the elements to mark as not selected. */ deselectedElementsIDs: string[]; /** * Whether all currently selected elements should be deselected. */ deselectAll?: boolean; } 2.8.3.2. SelectAllAction Used for selecting or deselecting all elements.\nCode interface SelectAllAction extends Action { /** * The kind of the action. */ kind = \u0026#39;allSelected\u0026#39;; /** * If `select` is true, all elements are selected, otherwise they are deselected. */ select: boolean; } 2.9. Element Hover 2.9.1. RequestPopupModelAction Triggered when the user hovers the mouse pointer over an element to get a popup with details on that element. This action is sent from the client to the server. The response is a SetPopupModelAction.\nCode interface RequestPopupModelAction extends Action { /** * The kind of the action. */ kind = \u0026#39;requestPopupModel\u0026#39;; /** * The identifier of the elements for which a popup is requested. */ elementId: string; /** * The bounds. */ bounds: Bounds; } 2.9.2. SetPopupModelAction Sent from the server to the client to display a popup in response to a RequestPopupModelAction. This action can also be used to remove any existing popup by choosing EMPTY_ROOT as root element.\nCode interface SetPopupModelAction extends Action { /** * The kind of the action. */ kind = \u0026#39;setPopupModel\u0026#39;; /** * The model elements composing the popup to display. */ newRoot: GModelRootSchema; } 2.10. Element Validation Validation in GLSP is performed by using validation markers. A marker represents the validation result for a single model element:\nCode interface Marker { /** * Short label describing this marker message, e.g., short validation message */ readonly label: string; /** * Full description of this marker, e.g., full validation message */ readonly description: string; /** * Id of the model element this marker refers to */ readonly elementId: string; /** * Marker kind, e.g., info, warning, error or custom kind */ readonly kind: string; } 2.10.1. RequestMarkersAction Action to retrieve markers for the specified model elements. Sent from the client to the server.\nCode interface RequestMarkersAction extends RequestAction { /** * The kind of the action. */ kind = \u0026#39;requestMarkers\u0026#39;; /** * The elements for which markers are requested, may be just the root element. */ elementsIDs: string[]; /** * The reason for this request, e.g. a `batch` validation or a `live` validation. */ reason?: string; } 2.10.2. SetMarkersAction Response to the RequestMarkersAction containing all validation markers. Sent from the server to the client. This action always sends the entire list of markers. Thus, clients can replace all markers for a specific reason with the new ones that have been sent with the same reason.\nCode interface SetMarkersAction extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;setMarkers\u0026#39;; /** * The list of markers to be set in the diagram editor. */ markers: Marker[]; /** * The reason for this response, e.g. a `batch` validation or a `live` validation. */ reason?: string; } 2.10.3. DeleteMarkersAction To remove markers for elements a client or server may send a DeleteMarkersAction with all markers that should be removed.\nCode interface DeleteMarkersAction extends Action { /** * The kind of the action. */ kind = \u0026#39;deleteMarkers\u0026#39;; /** * The list of markers that should be deleted. */ markers: Marker[]; } 2.11. Element Navigation GLSP makes no assumption about the type of navigation a user may want to perform. Thus a generic infrastructure is provided that the client and server can use to implement specific navigation types, e.g., navigation to documentation, implementation, etc. The type of navigation is identified by the targetTypeId.\nA client may request the targets for a specific type of navigation by querying the server to which the server will respond with a set of navigation targets. A NavigationTarget identifies the object we want to navigate to via its uri and may further provide a label to display for the client. Additionally, generic arguments may be used to to encode any domain- or navigation type-specific information.\nCode interface NavigationTarget { /** * URI to identify the object we want to navigate to. */ uri: string; /** * Optional label to display to the user. */ label?: string; /** * Domain-specific arguments that may be interpreted directly or resolved further. */ args?: Args; } 2.11.1. RequestNavigationTargetsAction Action that is usually sent from the client to the server to request navigation targets for a specific navigation type such as documentation or implementation in the given editor context.\nCode interface RequestNavigationTargetsAction extends RequestAction\u0026lt;SetNavigationTargetsAction\u0026gt; { /** * The kind of the action. */ kind = \u0026#39;requestNavigationTargets\u0026#39;; /** * Identifier of the type of navigation targets we want to retrieve, e.g., \u0026#39;documentation\u0026#39;, \u0026#39;implementation\u0026#39;, etc. */ targetTypeId: string; /** * The current editor context. */ editorContext: EditorContext; } 2.11.2. SetNavigationTargetsAction Response action from the server following a RequestNavigationTargetsAction. It contains all available navigation targets for the queried target type in the provided editor context. The server may also provide additional information using the arguments, e.g., warnings, that can be interpreted by the client.\nCode interface SetNavigationTargetsAction extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;setNavigationTargets\u0026#39;; /** * A list of navigation targets. */ targets: NavigationTarget[]; /** * Custom arguments that may be interpreted by the client. */ args?: Args; } 2.11.3. NavigateToTargetAction Action that triggers the navigation to a particular navigation target. This may be used by the client internally or may be sent from the server.\nCode interface NavigateToTargetAction extends Action { /** * The kind of the action. */ kind = \u0026#39;navigateToTarget\u0026#39;; /** * The target to which we navigate. */ target: NavigationTarget; } 2.11.4. ResolveNavigationTargetAction If a client cannot navigate to a target directly, a ResolveNavigationTargetAction may be sent to the server to resolve the navigation target to one or more model elements. This may be useful in cases where the resolution of each target is expensive or the client architecture requires an indirection.\nCode interface ResolveNavigationTargetAction extends RequestAction\u0026lt;SetResolvedNavigationTargetAction\u0026gt; { /** * The kind of the action. */ kind = \u0026#39;resolveNavigationTarget\u0026#39;; /** * The navigation target to resolve. */ navigationTarget: NavigationTarget; } 2.11.4. SetResolvedNavigationTargetAction An action sent from the server in response to a ResolveNavigationTargetAction. The response contains the resolved element ids for the given target and may contain additional information in the args property.\nCode interface SetResolvedNavigationTargetAction extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;setResolvedNavigationTarget\u0026#39;; /** * The element ids of the resolved navigation target. */ elementIds: string[]; /** * Custom arguments that may be interpreted by the client. */ args?: Args; } 2.11.5. NavigateToExternalTargetAction If a navigation target cannot be resolved or the resolved target is something that is not part of our source model, e.g., a separate documentation file, a NavigateToExternalTargetAction may be sent. Since the target it outside of the model scope such an action would be typically handled by an integration layer (such as the surrounding IDE).\nCode interface NavigateToExternalTargetAction extends Action { /** * The kind of the action. */ kind = \u0026#39;navigateToExternalTarget\u0026#39;; /** * The target to which we navigate. */ target: NavigationTarget; } 2.12. Element Type Hints Type hints are used to define what modifications are supported on the different element types. Conceptually type hints are similar to features of a model elements but define the functionality on a type level. The rationale is to avoid a client-server round-trip for user feedback of each synchronous user interaction.\nIn GLSP we distinguish between ShapeTypeHints and EdgeTypeHints. These hints specify whether an element can be resized, relocated and/or deleted. Optionally, they specify a list of element types that can be contained/connected by this element.\nCode interface TypeHint { /** * The identifier of an element. */ readonly elementTypeId: string; /** * Specifies whether the element can be relocated. */ readonly repositionable: boolean; /** * Specifies whether the element can be deleted */ readonly deletable: boolean; } interface ShapeTypeHint extends TypeHint { /** * Specifies whether the element can be resized. */ readonly resizable: boolean; /** * Specifies whether the element can be moved to another parent */ readonly reparentable: boolean; /** * The types of elements that can be contained by this element (if any) */ readonly containableElementTypeIds?: string[]; } interface EdgeTypeHint extends TypeHint { /** * Specifies whether the routing points of the edge can be changed * i.e. edited by the user. */ readonly routable: boolean; /** * Allowed source element types for this edge type * If not defined unknown element can be used as source element for this edge. */ readonly sourceElementTypeIds?: string[]; /** * Allowed targe element types for this edge type * If not defined unknown element can be used as target element for this edge. */ readonly targetElementTypeIds?: string[]; /** * Indicates whether this type hint is dynamic or not. Dynamic edge type hints * require an additional runtime check before creating an edge, when checking * source and target element types is not sufficient. */ readonly dynamic?: boolean; } 2.12.1. RequestTypeHintsAction Sent from the client to the server in order to request hints on whether certain modifications are allowed for a specific element type. The RequestTypeHintsAction is optional, but should usually be among the first messages sent from the client to the server after receiving the model via RequestModelAction. The response is a SetTypeHintsAction.\nCode interface RequestTypeHintsAction extends RequestAction\u0026lt;SetTypeHintsAction\u0026gt; { /** * The kind of the action. */ kind = \u0026#39;requestTypeHints\u0026#39;; } 2.12.2. SetTypeHintsAction Sent from the server to the client in order to provide hints certain modifications are allowed for a specific element type.\nCode interface SetTypeHintsAction extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;setTypeHints\u0026#39;; /** * The hints for shape types. */ shapeHints: ShapeTypeHint[]; /** * The hints for edge types. */ edgeHints: EdgeTypeHint[]; } 2.12.3. RequestCheckEdgeAction Sent from the client to the server to check wether the provided edge context information is valid i.e. creation of an edge with the given edge type and source/target element is allowed by the server. Typically this action is dispatched by edge creation tools in the creation phase of an edge that\u0026rsquo;s associated with a dynamic EdgeTypeHint.\nCode interface RequestCheckEdgeAction extends RequestAction\u0026lt;CheckEdgeResultAction\u0026gt; { kind: \u0026#39;requestCheckEdge\u0026#39;; /** * The element type of the edge being created. */ edgeType: string; /** * The ID of the edge source element. */ sourceElementId: string; /** * The ID of the edge target element to check. */ targetElementId?: string; } 2.12.4. CheckEdgeResultAction Sent from the server to the client as a response for a {@link RequestCheckEdgeAction}. It provides a boolean indicating whether the edge context information provided by the corresponding request action is valid i.e. creation of an edge with the given edge type and source/target element is allowed.\nCode interface CheckEdgeResultAction extends ResponseAction { kind: \u0026#39;checkEdgeTargetResult\u0026#39;; /** * true if the selected element is a valid target for this edge, * false otherwise. */ isValid: boolean; /** * The element type of the edge that has been checked. */ edgeType: string; /** * The ID of the source element of the edge that has been checked. */ sourceElementId: string; /** * The ID of the target element of the edge that has been checked. */ targetElementId?: string; } 2.13. Element Creation and Deletion 2.13.1. CreateNodeOperation Code In order to create a node in the model the client can send a CreateNodeOperation with the necessary information to create that node.\ninterface CreateNodeOperation extends CreateOperation { /** * The kind of the action. */ kind = \u0026#39;createNode\u0026#39;; /* * The location at which the operation shall be executed. */ location?: Point; /* * The container in which the operation shall be executed. */ containerId?: string; } 2.13.2. CreateEdgeOperation In order to create an edge in the model the client can send a CreateEdgeOperation with the necessary information to create that edge.\nCode interface CreateEdgeOperation extends CreateOperation { /** * The kind of the action. */ kind = \u0026#39;createEdge\u0026#39;; /* * The source element. */ sourceElementId: string; /* * The target element. */ targetElementId: string; } 2.13.3. DeleteElementOperation The client sends a DeleteElementOperation to the server to request the deletion of an element from the model.\nCode interface DeleteElementOperation extends Operation { /** * The kind of the action. */ kind = \u0026#39;deleteElement\u0026#39;; /** * The elements to be deleted. */ elementIds: string[]; } 2.14. Node Modification 2.14.1. ChangeBoundsOperation Triggers the position or size change of elements. This action concerns only the element\u0026rsquo;s graphical size and position. Whether an element can be resized or repositioned may be specified by the server with a TypeHint to allow for immediate user feedback before resizing or repositioning.\nCode interface ChangeBoundsOperation extends Operation { /** * The kind of the action. */ kind = \u0026#39;changeBounds\u0026#39;; /** * The new bounds of the respective elements. */ newBounds: ElementAndBounds[]; } 2.14.2. ChangeContainerOperation The client sends a ChangeContainerOperation to the server to request the execution of a changeContainer operation.\nCode interface ChangeContainerOperation implements Operation { /** * The kind of the action. */ kind = \u0026#39;changeContainer\u0026#39;; /** * The element to be changed. */ elementId: string; /** * The element container of the changeContainer operation. */ targetContainerId: string; /** * The graphical location. */ location?: string; } 2.15. Edge Modification 2.15.1. ReconnectEdgeOperation If the source and/or target element of an edge should be adapted, the client can send a ReconnectEdgeOperation to the server.\nCode interface ReconnectEdgeOperation extends Operation { /** * The kind of the action. */ kind = \u0026#39;reconnectEdge\u0026#39;; /** * The edge element that should be reconnected. */ edgeElementId: string; /** * The (new) source element of the edge. */ sourceElementId: string; /** * The (new) target element of the edge. */ targetElementId: string; /* * Additional arguments for custom behavior. */ args?: Args; } 2.15.2. ChangeRoutingPointsOperation An edge may have zero or more routing points that \u0026ldquo;re-direct\u0026rdquo; the edge between the source and the target element. In order to set these routing points the client may send a ChangeRoutingPointsOperation.\nCode interface ChangeRoutingPointsOperation extends Operation { /** * The kind of the action. */ kind = \u0026#39;changeRoutingPoints\u0026#39;; /** * The routing points of the edge (may be empty). */ newRoutingPoints: ElementAndRoutingPoints[]; } 2.16. Element Text Editing A common use case in diagrams is to query the user for textual input to perform a certain action, e.g., when editing the text on a label.\nTo support the validation of user input in the context of such an action before actually applying that user input, GLSP defines two actions: RequestEditValidationAction and SetEditValidationResultAction.\nCode interface ValidationStatus { /** * The severity of the validation returned by the server. */ readonly severity: ValidationStatus.Severity; /** * The validation status message which may be rendered in the view. */ readonly message?: string; /** * A potential error that encodes more details. */ readonly error?: ResponseError; } interface ResponseError { /** * Code identifying the error kind. */ readonly code: number; /** * Error message. */ readonly message: string; /** * Additional custom data, e.g., a serialized stacktrace. */ readonly data: Object; } namespace ValidationStatus { enum Severity { FATAL, ERROR, WARNING, INFO, OK, NONE } } 2.16.1. RequestEditValidationAction Requests the validation of the given text in the context of the provided model element. Typically sent from the client to the server.\nCode interface RequestEditValidationAction extends RequestAction\u0026lt;SetEditValidationResultAction\u0026gt; { /** * The kind of the action. */ kind = \u0026#39;requestEditValidation\u0026#39;; /** * Context in which the text is validated, e.g., \u0026#39;label-edit\u0026#39;. */ contextId: string; /** * Model element that is being edited. */ modelElementId: string; /** * Text that should be considered for the model element. */ text: string; } 2.16.2. SetEditValidationResultAction Response to a RequestEditValidationAction containing the validation result for applying a text on a certain model element.\nCode interface SetEditValidationResultAction extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;setEditValidationResult\u0026#39;; /** * Validation status. */ status: ValidationStatus; /* * Additional arguments for custom behavior. */ args?: Args; } 2.16.3. ApplyLabelEditOperation A very common use case in domain models is the support of labels that display textual information to the user. For instance, the GGraph model has support for labels that can be attached to a node, edge, or port, and that contain some text that is rendered in the view. To apply new text to such a label element the client may send an ApplyLabelEditOperation to the server.\nCode interface ApplyLabelEditOperation extends Operation { /** * The kind of the action. */ kind = \u0026#39;applyLabelEdit\u0026#39;; /** * Identifier of the label model element. */ labelId: string; /** * Text that should be applied on the label. */ text: string; } 2.17. Clipboard In GLSP the clipboard needs to be managed by the client but the conversion from the selection to be copied into a clipboard-compatible format is handled by the server. By default, GLSP use application/json as exchange format.\nCode type ClipboardData = { [format: string]: string }; 2.17.1. RequestClipboardDataAction Requests the clipboard data for the current editor context, i.e., the selected elements, in a clipboard-compatible format.\nCode interface RequestClipboardDataAction extends RequestAction\u0026lt;SetClipboardDataAction\u0026gt; { /** * The kind of the action. */ kind = \u0026#39;requestClipboardData\u0026#39;; /** * The current editor context. */ editorContext: EditorContext; } 2.17.2. SetClipboardDataAction Server response to a RequestClipboardDataAction containing the selected elements as clipboard-compatible format.\nCode interface SetClipboardDataAction extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;setClipboardData\u0026#39;; /** * The selected elements from the editor context as clipboard data. */ clipboardData: ClipboardData; } 2.17.3. CutOperation Requests a cut operation from the server, i.e., deleting the selected elements from the model. Before submitting a CutOperation a client should ensure that the cut elements are put into the clipboard.\nCode interface CutOperation extends Operation { /** * The kind of the action. */ kind = \u0026#39;cut\u0026#39;; /** * The current editor context. */ editorContext: EditorContext; } 2.17.4. PasteOperation Requests a paste operation from the server by providing the current clipboard data. Typically this means that elements should be created based on the data in the clipboard.\nCode interface PasteOperation extends Operation { /** * The kind of the action. */ kind = \u0026#39;paste\u0026#39;; /** * The current editor context. */ editorContext: EditorContext; /** * The clipboard data that should be pasted to the editor\u0026#39;s last recorded mouse position (see `editorContext`). */ clipboardData: ClipboardData; } 2.18. Undo / Redo A server usually keeps a command stack of all commands executed on the model. To navigate the command stack the following actions can be used.\n2.18.1. UndoAction Trigger an undo of the latest executed command.\nCode interface UndoAction { /** * The kind of the action. */ kind = \u0026#39;glspUndo\u0026#39;; } 2.18.2. RedoAction Trigger a redo of the latest undone command.\nCode interface RedoAction { /** * The kind of the action. */ kind = \u0026#39;glspRedo\u0026#39;; } 2.19. Contexts A context is a dedicated space in the client that is identified via a unique id. Context actions are a specific set of actions that are available in that context id. At the moment we support three such contexts:\n The Context Menu with the context id context-menu The Command Palette with the context id command-palette The Tool Palette with the context id tool-palette 2.19.1. RequestContextActions Code The RequestContextActions is sent from the client to the server to request the available actions for the context with id contextId.\ninterface RequestContextActions extends RequestAction\u0026lt;SetContextActions\u0026gt; { /** * The kind of the action. */ kind = \u0026#39;requestContextActions\u0026#39;; /** * The identifier for the context. */ contextId: string; /** * The current editor context. */ editorContext: EditorContext; } 2.19.2. SetContextActions The SetContextActions is the response to a RequestContextActions containing all actions for the queried context.\nCode interface SetContextActions extends ResponseAction { /** * The kind of the action. */ kind = \u0026#39;setContextActions\u0026#39;; /** * The actions available in the queried context. */ readonly actions: LabeledAction[]; /** * Custom arguments. */ args: ?Args; } 2.19.3. Context Menu The context menu is an overlay that is triggered by a right click from the user. The menu may be filled with actions from the client but may also be filled with actions from the server. If server actions are to be used, the client needs to send a RequestContextActions action with context id context-menu and handle the returned actions from the SetContextActions response accordingly, e.g., rendering them in a context menu.\n2.19.4. Command Palette The command palette is an \u0026ldquo;auto-complete\u0026rdquo; widget that is triggered when the user hits Ctrl+Space. The menu may be filled with actions from the client but may also be filled with actions from the server. If server actions are to be used, the client needs to send a RequestContextActions action with context id command-palette and handle the returned actions from the SetContextActions response accordingly, i.e., rendering them in a auto-complete widget.\n2.19.5. Tool Palette The tool palette is a widget on the graph\u0026rsquo;s canvas that displays a set of tools and actions that the user can use to interact with the model. As such the tool palette consists of two parts: tools and labeled actions.\nA tool is a uniquely identified functionality that can be either enabled or disabled. Tools can be activated and de-activated from the user by clicking their rendered representation in the platte or may be activated using dedicated actions.\nCode interface Tool { /** * Unique tool id. */ readonly id: string; /** * Notifies the tool to become active. */ enable(): void; /** * Notifies the tool to become inactive. */ disable(): void; } By default, the tool palette in GLSP includes the following tools in the palette:\n Default Tool (Selection Tool) Mouse Delete Tool Validation Tool The supported actions of the tool palette come from the server. If server actions are to be used, the client needs to send a RequestContextActions action with context id tool-palette and handle the returned actions from the SetContextActions response accordingly, e.g., rendering them in the tool palette. A user may click on any of the entries in the tool palette to trigger the corresponding action.\nFor creating new elements we provide two dedicated trigger actions that can be sent from the server to activate and configure the Node Creation Tool or the Edge Creation Tool respectively. This indirection is necessary as the user, after clicking on the respective action, still needs to provide additional information, i.e., the location of the new node or which elements should be connected through an edge. After all information is available, the actual creation operation is triggered.\n2.19.5.1. TriggerNodeCreationAction Triggers the enablement of the tool that is responsible for creating nodes and initializes it with the creation of nodes of the given elementTypeId.\nCode interface TriggerNodeCreationAction extends Action { /** * The kind of the action. */ kind = \u0026#39;triggerNodeCreation\u0026#39;; /** * The type of node that should be created by the node creation tool. */ elementTypeId: string; /** * Custom arguments. */ args?: Args; } 2.19.5.2. TriggerEdgeCreationAction Triggers the enablement of the tool that is responsible for creating edges and initializes it with the creation of edges of the given elementTypeId.\nCode interface TriggerEdgeCreationAction extends Action { /** * The kind of the action. */ kind = \u0026#39;triggerEdgeCreation\u0026#39;; /** * The type of edge that should be created by the edge creation tool. */ elementTypeId: string; /** * Custom arguments. */ args?: Args; } ","permalink":"https://www.eclipse.dev/glsp/documentation/protocol/","tags":null,"title":"GLSP Protocol"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/glsp/search/","tags":null,"title":"Search"},{"categories":null,"contents":"Articles Eclipse GLSP 2: Elevating Web-based Diagram Editors Eclipse GLSP 1.0 Release is here! Eclipse GLSP 1.0: Leading-edge diagram editor support Eclipse GLSP 1.0: TypeScript support for GLSP server implementations Web-based diagram editor features in Eclipse GLSP GLSP: Diagrams in VS Code, Theia, Eclipse and plain HTML Diagram editors in Theia and VS Code with Eclipse GLSP A diagram editor framework for VS Code A hands-on tutorial for Eclipse GLSP A minimal diagram editor example for Eclipse GLSP Building diagram editors in Eclipse Theia with GLSP The workflow diagram example for Eclipse GLSP A web-based modeling tool based on Eclipse Theia and GLSP An introduction to the Graphical Language Server Protocol/Platform (Eclipse GLSP) Videos EclipseCon 2023: Diagram Editors Boosted: Collaborative, Testable and Accessible diagrams with Eclipse GLSP EclipseCon 2022: Diagram Editors with GLSP: Why flexibility is key MDE Net 2022: Building Modern Diagram Editors with Eclipse GLSP Cloud IDE Days 2022: Diagram Editors in Cloud IDEs EclipseCon 2021: Diagram editors with Eclipse GLSP - 1.0 Cloud Tool Time: Diagram editors in Theia and VS Code with Eclipse GLSP EclipseCon 2020: Diagram editors in the web with Eclipse GLSP EclipseCon 2020: Ecore tools in the cloud - behind the scenes EclipseCon 2020: Web-based modeling tools with EMF.cloud and GLSP Eclipse Virtual Meet Up 2019: Diagrams in web and space with GLSP EclipseCon 2019: Diagrams in web and space with GLSP EclipseCon Europe 2018: Towards a Graphical Language Server Protocol for Diagrams? ","permalink":"https://www.eclipse.dev/glsp/documentation/additionals/","tags":null,"title":"More Articles \u0026 Videos"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/glsp/categories/","tags":null,"title":"Categories"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/glsp/contact/","tags":null,"title":"Contact"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/glsp/documentation/","tags":null,"title":"Documentation"},{"categories":null,"contents":"An example GLSP diagram including one GLSP server and several diagram client integrations (VS Code, Eclipse Theia, plain HTML and Eclipse desktop IDE). The example can be used to try out the GLSP features in a running version. Most components of GLSP, such as the GLSP server framework or the Theia integration implement the workflow diagram as a consistent example. As the example is fully open source, you can use the example as a blueprint for a custom implementation of a GLSP diagram. The example diagram is a simplified flow chart with different types of nodes and edges (see screen shot below) To launch/browse the example, please use the links below leading to the components of the workflow diagram example. You will need the workflow GLSP server (there is only one) and at least one client integration (select the one you are interested in). Launch the server first and then the client of your choice to try the example. Alternatively, you can try the workflow example online embedded into an example tool (see last option below) ","permalink":"https://www.eclipse.dev/glsp/examples/","tags":null,"title":"Examples"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/glsp/gallery/","tags":null,"title":"Gallery"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/glsp/","tags":null,"title":"GLSP"},{"categories":null,"contents":"Support for GLSP and for projects adopting GLSP is provided by ","permalink":"https://www.eclipse.dev/glsp/support/","tags":null,"title":"Support"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/glsp/tags/","tags":null,"title":"Tags"}]