PageSourceSearch

https://docs.flarum.org/assets/js/745d3f51.8e903fbb.js

js flarum.org collected 2026-09-24 18:16:55 UTC 12,165 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkflarum_docs=globalThis.webpackChunkflarum_docs||[]).push([[9360],{378(e,n,s){s.r(n),s.d(n,{assets:()=>r,contentTitle:()=>l,default:()=>h,frontMatter:()=>a,metadata:()=>o,toc:()=>c});const o=JSON.parse('{"id":"extend/interactive-components","title":"Interactive Components","description":"Often, you\'ll want to trigger interactive components in addition to whatever content/animations you have on a given page.","source":"@site/docs/extend/interactive-components.md","sourceDirName":"extend","slug":"/extend/interactive-components","permalink":"/extend/interactive-components","draft":false,"unlisted":false,"editUrl":"https://github.com/flarum/docs/tree/main/docs/extend/interactive-components.md","tags":[],"version":"current","frontMatter":{},"sidebar":"extendSidebar","previous":{"title":"Frontend Pages and Resolvers","permalink":"/extend/frontend-pages"},"next":{"title":"Haptic Feedback","permalink":"/extend/haptic-feedback"}}');var t=s(4848),i=s(8453);const a={},l="Interactive Components",r={},c=[{value:"Alerts",id:"alerts",level:2},{value:"Modals",id:"modals",level:2}
1,{value:"Composer",id:"composer",level:2},{value:"Composer Editor",id:"composer-editor",level:3}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"interactive-components",children:"Interactive Components"})}),"\n",(0,t.jsx)(n.p,{children:"Often, you'll want to trigger interactive components in addition to whatever content/animations you have on a given page.\nDepending on the nature of your extension, you may want to define custom interactive elements or reuse or extend existing ones."}),"\n",(0,t.jsxs)(n.p,{children:["All ",(0,t.jsx)(n.a,{href:"/extend/frontend#components",children:"components"})," and ",(0,t.jsx)(n.a,{href:"/extend/frontend#flarum-utils",children:"utilities"})," from Flarum core and bundled extensions are exported, making them available for reuse in other extensions. A full list is available in our ",(0,t.jsx)(n.a,{href:"https://api.docs.flarum.org/js/2.x/modules/flarum.html",children:"API documentation"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"alerts",children:"Alerts"}),"\n",(0,t.jsxs)(n.p,{children:["Alerts are managed by a global instance of ",(0,t.jsx)(n.a,{href:"https://api.docs.flarum.org/js/2.x/classes/flarum.common_states_alertmanagerstate.alertmanagerstate",children:(0,t.jsx)(n.code,{children:"AlertManagerState"})}),", which is accessible via ",(0,t.jsx)(n.code,{children:"app.alerts"})," on both the ",(0,t.jsx)(n.code,{children:"forum"})," and ",(0,t.jsx)(n.code,{children:"admin"})," frontends. It has 3 publicly accessible methods:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"app.alerts.show"})," will add a new alert, and return a key which can later be used to dismiss that alert. It has 3 overloads:","\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"app.alerts.show(children)"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"app.alerts.show(attrs, children)"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"app.alerts.show(componentClass, attrs, children)"})}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"app.alerts.dismiss(key)"})," will dismiss an active alert with the given key, if one exists."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"app.alerts.clear()"})," will dismiss all alerts."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["Typically, you won't need a custom component for alerts; however, if you could like, you can provide one. You'll probably want it to inherit ",(0,t.jsx)(n.code,{children:"flarum/common/components/Alert"}),"."]}),"\n",(0,t.jsx)(n.p,{children:"The following attrs are useful to keep in mind:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["The ",(0,t.jsx)(n.code,{children:"type"})," attr will apply the ",(0,t.jsx)(n.code,{children:"Alert--{type}"})," css class. ",(0,t.jsx)(n.code,{children:"success"})," will yield a green alert, ",(0,t.jsx)(n.code,{children:"error"})," a red alert, and an empty ",(0,t.jsx)(n.code,{children:"type"})," a yellow alert."]}),"\n",(0,t.jsxs)(n.li,{children:["The ",(0,t.jsx)(n.code,{children:"dismissible"})," attr will dictate whether a dismiss button will be shown."]}),"\n",(0,t.jsxs)(n.li,{children:["The ",(0,t.jsx)(n.code,{children:"ondismiss"})," attr can be used to provide a callback which will run when the alert is dismissed."]}),"\n",(0,t.jsxs)(n.li,{children:["Components provided in the ",(0,t.jsx)(n.code,{children:"controls"})," attr will be shown after alert children."]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"modals",children:"Modals"}),"\n",(0,t.jsxs)(n.p,{children:["Modals are managed by a global instance of ",(0,t.jsx)(n.a,{href:"https://api.docs.flarum.org/js/2.x/classes/flarum.common_states_modalmanagerstate.modalmanagerstate",children:(0,t.jsx)(n.code,{children:"ModalManagerState"})}),", which is accessible via ",(0,t.jsx)(n.code,{children:"app.modal"})," on both the ",(0,t.jsx)(n.code,{children:"forum"})," and ",(0,t.jsx)(n.code,{children:"admin"})," frontends. It has 2 publicly accessible methods:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"app.modal.show(componentClass, attrs)"})," will show a modal using the given component class and attrs. If called while a modal is already open, it will replace the currently open modal."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"app.modal.close()"})," will close the modal if one is currently active."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["As opposed to alerts, most modals will use a custom class, inheriting ",(0,t.jsx)(n.code,{children:"flarum/common/components/Modal"}),". For example:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-jsx",children:"import Modal from 'flarum/common/components/Modal';
1\n\nexport default class CustomModal extends Modal {\n  // All true by default. These control whether the modal can be dismissed via the close button, the Esc key, and clicking the backdrop, respectively.\n  static isDismissibleViaCloseButton = true;\n  static isDismissibleViaEscKey = true;\n  static isDismissibleViaBackdropClick = true;\n\n  className() {\n    // Custom CSS classes to apply to the modal\n    return 'custom-modal-class';\n  }\n\n  title() {\n    // Content to show in the modal's title bar\n    return <p>Custom Modal</p>;\n  }\n\n  content() {\n    // Content to show in the modal's body\n    return <p>Hello World!</p>;\n  }\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Modals with forms inherit ",(0,t.jsx)(n.code,{children:"flarum/common/components/FormModal"}),". This class provides a ",(0,t.jsx)(n.code,{children:"onsubmit"})," method which is called when the submit button is clicked:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-jsx",children:"import FormModal from 'flarum/common/components/FormModal';\n\nexport default class CustomFormModal extends FormModal {\n  // All true by default. These control whether the modal can be dismissed via the close button, the Esc key, and clicking the backdrop, respectively.\n  static isDismissibleViaCloseButton = true;\n  static isDismissibleViaEscKey = true;\n  static isDismissibleViaBackdropClick = true;\n\n  className() {\n    // Custom CSS classes to apply to the modal\n    return 'custom-modal-class';\n  }\n\n  title() {\n    // Content to show in the modal's title bar\n    return <p>Custom Modal</p>;\n  }\n\n  content() {\n    // Content to show in the modal's body\n    return <p>Hello World!</p>;\n  }\n\n  onsubmit() {\n    // If your modal contains a form, you can add form processing logic here.\n  }\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["More information about methods available to override is available in our ",(0,t.jsx)(n.a,{href:"https://api.docs.flarum.org/js/2.x/classes/flarum.common_components_modal.modal",children:"API documentation"}),"."]}),"\n",(0,t.jsxs)(n.admonition,{type:"info",children:[(0,t.jsx)(n.mdxAdmonitionTitle,{children:(0,t.jsx)(n.a,{href:"https://github.com/flarum/cli",children:"Flarum CLI"})}),(0,t.jsx)(n.p,{children:"You can use the CLI to automatically generate a modal:"}),(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"$ flarum-cli make frontend modal\n"})})]}),"\n",(0,t.jsx)(n.h2,{id:"composer",children:"Composer"}),"\n",(0,t.jsx)(n.p,{children:"Since Flarum is a forum, we need tools for users to be able to create and edit posts and discussions. Flarum accomplishes this through the floating composer component."}),"\n",(0,t.jsxs)(n.p,{children:["The composer is managed by a global instance of ",(0,t.jsx)(n.a,{href:"https://api.docs.flarum.org/js/2.x/classes/flarum.common_states_modalmanagerstate.modalmanagerstate",children:(0,t.jsx)(n.code,{children:"ComposerState"})}),", which is accessible via ",(0,t.jsx)(n.code,{children:"app.composer"})," on the ",(0,t.jsx)(n.code,{children:"forum"})," frontend. Its most important public methods are:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"app.composer.load(componentClass, attrs)"})," will load in a new composer type. If a composer is already active, it will be replaced."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"app.composer.show()"})," will show the composer if it is currently hidden."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"app.composer.close()"})," will close and reset the composer after confirming with the user."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"app.composer.hide()"})," will close and reset the composer without confirming with the user."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"app.composer.bodyMatches(componentClass, attrs)"})," will check if the currently active composer is of a certain type, and whether its atts match optionally provided attrs."]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"The full list of public methods is documented in the API docs linked above."}),"\n",(0,t.jsxs)(n.p,{children:["Because the composer can be used for various different actions (starting a discussion, editing a post, replying to a discu
1ssion, etc.), its fields may vary depending as usage.\nThis is done by splitting code for each usage into a subclass of ",(0,t.jsx)(n.code,{children:"flarum/forum/components/ComposerBody"}),". This component class must be provided when loading a composer."]}),"\n",(0,t.jsx)(n.h3,{id:"composer-editor",children:"Composer Editor"}),"\n",(0,t.jsxs)(n.p,{children:["The actual editor is yet another component, ",(0,t.jsx)(n.a,{href:"https://api.docs.flarum.org/js/2.x/classes/flarum.common_components_texteditor.texteditor",children:(0,t.jsx)(n.code,{children:"flarum/common/components/TextEditor"})}),'.\nIts state can be programatically accessed via an "editor driver", which implements ',(0,t.jsx)(n.a,{href:"https://github.com/flarum/framework/blob/main/framework/core/js/src/common/utils/EditorDriverInterface.ts",children:(0,t.jsx)(n.code,{children:"EditorDriverInterface"})}),".\nThis is globally available for the current composer via ",(0,t.jsx)(n.code,{children:"app.composer.editor"}),", and allows extensions to programatically read, insert, and modify the current contents, selections, and cursor position of the active composer's text editor."]})]})}function h(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453(e,n,s){s.d(n,{R:()=>a,x:()=>l});var o=s(6540);const t={},i=o.createContext(t);function a(e){const n=o.useContext(i);return o.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),o.createElement(i.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.