On this page ...

Making an Editor for your DSL

At last, your patience is rewarded. In this step of the tutorial we’re going to make the editor look great!

The default editor

Let’s create a file called edu-main.edit in the src/defs folder, and define a default editor for this model unit. Please enter the following line.

// Education/lesson2-defs/edu-topics.edit#L3-L3

editor default

There—that’s done!

No, of course we are not done. Just kidding—and showing you that Freon will add a default projection for any concept that is not included in the editor definition. You can build your editor concept by concept.

In fact, the editor that is generated in Lesson 1 is completely based on the defaults generated by Freon.

Image 'tutorial/Tutorial-lesson2-screenshot1.png' seems to be missing
Figure 2. Editor from lesson 1

A projection for the Topic model unit

A complete editor definition is built from a number of projections. To define a single projection we need the name of the concept, some curly brackets, and—last but not least—a pair of square brackets. Everything that goes between the square brackets will be taken as literally as possible in the editor. That is, if there is indentation or literal text, this will be projected as unchangeable strings.

The first step is to focus on the Topic model unit. Let’s state how this model unit should look. The projection for Topic consists of four parts.

  • A line with the fixed string SiteGroup: followed by the name of the site group. For the latter we use a syntax similar to the smart strings in TypeScript: ${self.main}. You may leave out the self. part. It indicates that we refer to the property of the Topic object that is projected. For clarity, we will use this prefix throughout the tutorial.
  • A line with the fixed string Topic: followed by the name of the unit.
  • The third line is similar, but projects the description property of the model unit. Next, we add an empty line which—like the indentation—will show in the editor.
  • The last part of the definition gives the projection of the pages property, which is a list. By adding the keyword vertical we tell Freon to project the list vertically.
// Education/lesson2-defs/edu-topics.edit#L3-L16

editor default

Topic {[
    SiteGroup: ${self.main}
    Topic: ${self.name}
    Topic description: ${self.description}

    Pages:
    ${self.pages vertical }
]}

Page {[
    ${self.name}
]}

The Possibilities for Lists

Above, we projected the pages as a vertical list. There are many options to project lists. Not all lists need to be projected vertically—use the keyword horizontal to display all list elements on a single line. The keyword may also be omitted; the default value is vertical.

In the next projection for the Theory concept, we project two lists: self.content and self.questions. Notice that we added something to the projection of self.content in the definition above: terminator[== END OF LINE]. It means that after every element of the list the string == END OF LINE is projected. Note that the line of dashes will also appear in the editor.

Theory {[
    ----------------------------------------------------
    Theory
        ${self.content vertical terminator[== END OF LINE]}

    Questions:
        ${self.questions vertical}
]}

Instead of using a terminator, you could also opt for a separator, where the character or string is projected between every element of the list, or an initiator, which projects the character or string before each element.

Here are some examples of how you can tweak the display.

    Theory
        ${self.content vertical separator [.]}

    Theory
        ${self.content horizontal terminator [====]}

    Theory
        ${self.content vertical initiator [Line]}

Go ahead and try the different options.

Inherited Projections

Remember that we defined the concept Page to be abstract, and there were several concepts that inherit from Page? Of course, we can build inherited projection definitions as well. We have defined the projection for the abstract concept Page as follows. It’s nothing fancy, but you could do more if you like.

// Education/lesson2-defs/edu-topics.edit#L14-L16

Page {[
    ${self.name}
]}

Now look at how we incorporate this projection in the projection of one of Page’s children using this syntax: [=>Page]. It means that we include the default projection of Page right there. Note that we can use inherited properties, like questions, as expected in the projection.

// Education/lesson2-defs/edu-topics.edit#L18-L25

Theory {[
    ----------------------------------------------------
    Theory [=>Page]
        ${self.content vertical terminator[== END OF LINE]}

    Questions:
        ${self.questions vertical}
]}

Now we can almost finish the projection for this model unit by adding the following lines. Each concept that inherits from Page is defined, as well as the questions and content parts of Page.

// Education/lesson2-defs/edu-topics.edit#L27-L76

Video {[
    ----------------------------------------------------
    Video [=>Page]
        Maybe this video will help you understand.
        ${self.url}

    Questions:
        ${self.questions vertical}
]}

WorkSheet {[
    ----------------------------------------------------
    Worksheet [=>Page]
        See if you can answer the following questions.

    Questions:
        ${self.questions vertical}
]}

ExamplePage {[
    ----------------------------------------------------
    Example [=>Page]
        ${self.content}

        Now, please, answer the following questions.

    Questions:
        ${self.questions vertical}
]}

InDepthMaterial {[
    ----------------------------------------------------
    InDepthMaterial [=>Page]
        ${self.content}

        Test your understanding by answering the following questions.

    Questions:
        ${self.questions vertical}
]}

Question {[
    ${self.name}
        ${self.content}
        Correct Answer: ${self.correctAnswer}
]}

Line {
    [${self.content}]
}

Triggers

The last thing to learn in this lesson is how we can make editing easier for the user. Remember that we needed a concept for numeric fractions? It’s called Fraction, and it is formed by combining two numbers: a numerator and a denominator. This is how the concept is defined in the .ast file.

// Education/lesson2-defs/edu-topics.ast#L53-L56

concept Fraction base NumberConcept {
    numerator: number;
    denominator: number;
}

In Freon, the user must first choose the Fraction option from a dropdown menu before they can enter any numbers. To avoid this extra step, we can tell Freon that when a certain key or string is entered, it should automatically create an instance of the associated concept. The key or string to be entered is called the trigger.

For the Fraction concept we define the trigger to be a forward slash. If the user wants to add a correct answer to a question, where the possibilities are either a SimpleNumber or a Fraction, entering / will produce a Fraction instance. So, the last two projections are defined as follows.

// Education/lesson2-defs/edu-topics.edit#L78-L85

SimpleNumber {
    [${self.value}]
}

Fraction {
    [${numerator} / ${denominator}]
    trigger = "/"
}

Now go ahead, generate a new editor, and have a look. Your editor should now look like this—much better than the result from Lesson 1!

Image 'tutorial/Tutorial-lesson2-screenshot2.png' seems to be missing
Figure 3. Editor after adding some projection definitions

When you’ve gotten this far, it might be a good idea to try for yourself what happens if you change the projection—for instance, switching the order of the lines. But don’t forget to get back to the next part of the tutorial, where we’ll have more fun with defining projections.

© 2018 - 2025 Freon contributors - Freon is open source under the MIT License.