DEV Community

loading...

haxHooks(): How elements can supply their own editing experience in HAX now!

Bryan Ollendyke
@elmsln @haxcamp @btopro #HAXTheWeb #drupal #webcomponents #edtech ✻ Full stack unicorn Adjunct professor teaching about webdev, ethics, and everything in between
・3 min read

HAX the web is a tag and a movement to replace the way WYSIWYG editors of the past worked and replace it with pure design assets that can plug directly into any editing experience. This week, after refactoring our internals the last few weeks, I have added a new construct in our integration arsenal.

What we've had: haxProperties

haxProperties is a static get method that elements wanting to integrate with HAX implement. By having this method and supplying our schema, our editor knows how to build headless forms to manipulate your design asset in context. Example:

  static get haxProperties() {
    return {
      canScale: true,
      canPosition: true,
      canEditSource: false,
      gizmo: {
        title: "Multiple choice",
        description: "Multiple choice self check",
        icon: "hax:multiple-choice",
        color: "purple",
        groups: ["Instructional"],
        handles: [],
        meta: {
          author: "ELMS:LN",
        },
      },
      settings: {
        configure: [
          {
            property: "title",
            title: "Title",
            description: "The title of the element",
            inputMethod: "textfield",
          },
          {
            property: "hideTitle",
            title: "Hide title",
            description: "Whether or not to display the title",
            inputMethod: "boolean",
          },
          {
            property: "question",
            title: "Question",
            description: "Question for users to respond to.",
            inputMethod: "textfield",
          },
          {
            property: "randomize",
            title: "Randomize",
            description: "Randomize the answers dynamically",
            inputMethod: "boolean",
          },
          {
            property: "answers",
            title: "Answer set",
            description: "Answers in a multiple choice",
            inputMethod: "array",
            itemLabel: "label",
            properties: [
              {
                property: "correct",
                title: "Correct",
                description: "If this is correct or not",
                inputMethod: "boolean",
              },
              {
                property: "label",
                title: "Answer",
                description: "Possible answer to the question",
                inputMethod: "textfield",
                required: true,
              },
            ],
          },
          {
            property: "correctText",
            title: "Correct feedback",
            description: "Feedback when they get it right",
            inputMethod: "textfield",
          },
          {
            property: "correctIcon",
            title: "Correct icon",
            description: "Icon to display when correct answer happens",
            inputMethod: "iconpicker",
            options: [
              "icons:trending-flat",
              "icons:launch",
              "icons:pan-tool",
              "icons:link",
              "icons:check",
              "icons:favorite",
              "icons:thumb-up",
              "icons:thumb-down",
              "icons:send",
            ],
          },
          {
            property: "incorrectText",
            title: "Incorrect feedback",
            description: "Feedback when they get it wrong",
            inputMethod: "textfield",
          },
          {
            property: "incorrectIcon",
            title: "Incorrect icon",
            description: "Icon to display when wrong answer happens",
            inputMethod: "iconpicker",
            options: [
              "icons:trending-flat",
              "icons:launch",
              "icons:pan-tool",
              "icons:link",
              "icons:check",
              "icons:favorite",
              "icons:thumb-up",
              "icons:thumb-down",
              "icons:send",
            ],
          },
          {
            property: "quizName",
            title: "Name of the quiz",
            description: "Quiz name passed in",
            inputMethod: "textfield",
          },
        ],
        advanced: [
          {
            property: "checkLabel",
            title: "Check answers label",
            description: "Label for getting solution feedback",
            inputMethod: "textfield",
          },
          {
            property: "resetLabel",
            title: "Reset label",
            description: "label for the reset button",
            inputMethod: "textfield",
          },
        ],
      },
      saveOptions: {
        unsetAttributes: [
          "__utils",
          "displayed-answers",
          "displayedAnswers",
          "colors",
        ],
      },
    };
  }
Enter fullscreen mode Exit fullscreen mode

The above is how we've worked with HAX to date. This is enough to tell HAX that it can be placed in the page, how to present it's forms, and how to clean up data when saved. It's awesome, but inflexible (by design).

Introducing haxHooks

haxHooks is a method implemented on your design asset web components that allows it to deeply tie into HAX and visa versa. They provide the ability to mix state and respond to events in a consistent way across the ecosystem.

Here's an example from another element now implementing haxHooks, this time to interrupt clicking to open a link that makes sense for the design asset but would make the element in-editable when trying to click to highlight it in HAX.

  /**
   * Implements haxHooks to tie into life-cycle if hax exists.
   */
  haxHooks() {
    return {
      editModeChanged: "haxeditModeChanged",
      activeElementChanged: "haxactiveElementChanged",
    };
  }
  /**
   * double-check that we are set to inactivate click handlers
   * this is for when activated in a duplicate / adding new content state
   */
  haxactiveElementChanged(el, val) {
    if (val) {
      this._haxstate = val;
    }
  }
  /**
   * Set a flag to test if we should block link clicking on the entire card
   * otherwise when editing in hax you can't actually edit it bc its all clickable.
   * if editMode goes off this helps ensure we also become clickable again
   */
  haxeditModeChanged(val) {
    this._haxstate = val;
  }
  /**
   * special support for HAX since the whole card is selectable
   */
  _clickCard(e) {
    if (this._haxstate) {
      // do not do default
      e.preventDefault();
      e.stopPropagation();
      e.stopImmediatePropagation();
    }
  }
Enter fullscreen mode Exit fullscreen mode

In this code we can see that this._haxstate is set to true (default false) when HAX has said that it is enabled and working on the asset. When saving, we can disable this flag based on editMode changing in HAX.

This video goes deep in the weeds on how this works and what's now possible as a result

Discussion (0)