Skip to content

Custom $dotapp library

Open the live demo at /documentation/examples/run/library.

Introduction

DotApp is not “PHP plus a random jQuery file”. The same request model (load, CRC, parseReply) is how you extend the frontend. You write a vanilla widget, then hang it on $dotapp with $dotapp().fn('name', fn). Put the file in your module assets. Never edit app/parts/js/.

The live Examples module ships exNotify (factory toasts), exCopy (per-button clipboard), exStepper (one node → API), and exPing (widget that POSTs to PHP).

Boot events

Libraries register on dotapp-register. Page scripts run on dotapp. Load order:

<script src="/assets/dotapp/dotapp.js"></script>
<script src="/assets/modules/Examples/js/ex-ui.js"></script>
<script src="/assets/modules/Examples/js/library.js"></script>

Guard double registration with isRegistered and catch an error message that contains already registered.

Registering fn()

Factory (empty $dotapp()): toasts and dialogs. Per-element: one node returns the widget API, many nodes return this.

(function () {
  var isRegistered = false;
  var runMe = function ($dotapp) {
    if (isRegistered) return;
    isRegistered = true;
    $dotapp().fn("exNotify", function (opts) {
      if (opts && typeof opts === "object") {
        return ExNotify.show(opts);
      }
      return this;
    });
    $dotapp().fn("exStepper", function (options) {
      var els = this.getElements();
      if (els.length !== 1) {
        throw new Error("exStepper requires exactly one element");
      }
      var el = els[0];
      if (!el._exStepper) el._exStepper = new ExStepper(el, options || {});
      return el._exStepper;
    });
  };
  if (window.$dotapp) runMe(window.$dotapp);
  else window.addEventListener("dotapp-register", function () { runMe(window.$dotapp); }, { once: true });
})();

Page code then does $dotapp().exNotify({ title: "Saved", text: "…" }) and $dotapp("#qty").exStepper(). Do not wrap $.fn.plugin. Rewrite in vanilla DOM.

Calling PHP from the widget

Inside the library, HTTP goes through this.load + parseReply. Never fetch a DotApp endpoint.

ExPing.prototype.send = function () {
  var self = this;
  this.dotApp.load(this.settings.url, "POST", {},
    function (raw) {
      var reply = self.dotApp.parseReply(raw);
      ExNotify.show({ title: "PHP", text: (reply && reply.message) ? reply.message : "No reply" });
    }
  );
};

PHP answers with crcCheck() and ajaxReply:

public static function ping($request)
{
    if (!$request->crcCheck()) {
        return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Bad request'], 400);
    }
    return DotApp::DotApp()->ajaxReply([
        'status' => 1,
        'message' => 'PHP answered at ' . date('H:i:s'),
    ], 200);
}

CSS

Keep widget classes on {modulename}_* or a shared demo sheet. The live styles are in /app/modules/Examples/assets/css/examples.css and are served as /assets/modules/Examples/css/examples.css. Toast stack, stepper, and snippet block live there so you can copy the look into your module.

Live demo

/documentation/examples/run/library — toasts, copy, stepper, and a PHP ping. Source: ex-ui.js, library.js, Controllers/Library.php.