Browse Source

Documentation - client-side Stimulus - add detailed integration example

LB Johnston 1 year ago
parent
commit
a17f7c3e1c
1 changed files with 122 additions and 0 deletions
  1. 122 0
      docs/extending/extending_client_side.md

+ 122 - 0
docs/extending/extending_client_side.md

@@ -205,6 +205,128 @@ def editor_js():
 
 You should be able to see that on your Blog Pages, the introduction field will now have a small `output` element showing the count and max words being used.
 
+(extending_client_side_stimulus_widget)=
+
+#### A more complex widget example
+
+For more complex widgets we can now integrate additional libraries whenever the widget appears in the rendered HTML, either on initial load or dynamically without the need for any inline `script` elements.
+
+In this example we will build a color picker widget using the [Coloris](https://coloris.js.org/) JavaScript library with support for custom widget options.
+
+First, let's start with the HTML, building on the [Django widgets](django:ref/forms/widgets) system that Wagtail supports for `FieldPanel` and `FieldBlock`. Using the `build_attrs` method, we build up the appropriate Stimulus data attributes to support common data structures being passed into the controller.
+
+Observe that we are using `json.dumps` for complex values (a list of strings in this case), Django will automatically escape these values when rendered to avoid common causes of insecure client-side code.
+
+```py
+# myapp/widgets.py
+import json
+
+from django.forms import Media, TextInput
+
+from django.utils.translation import gettext as _
+
+class ColorWidget(TextInput):
+    """
+    See https://coloris.js.org/
+    """
+
+    def __init__(self, attrs=None, swatches=[], theme='large'):
+        self.swatches = swatches
+        self.theme = theme
+        super().__init__(attrs=attrs);
+
+    def build_attrs(self, *args, **kwargs):
+        attrs = super().build_attrs(*args, **kwargs)
+        attrs['data-controller'] = 'color'
+        attrs['data-color-theme-value'] = self.theme
+        attrs['data-color-swatches-value'] = json.dumps(swatches)
+        return attrs
+
+    @property
+    def media(self):
+        return Media(
+            js=[
+                # load the UI library
+                "https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.js",
+                # load controller JS
+                "js/color-controller.js",
+            ],
+            css={"all": ["https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.css"]},
+        )
+```
+
+For the Stimulus controller, we pass the values through to the JavaScript library, including a reference to the controlled element via `this.element.id`.
+
+```javascript
+// myapp/static/js/color-controller.js
+
+class ColorController extends window.StimulusModule.Controller {
+    static values = { swatches: Array, theme: String };
+
+    connect() {
+        // create
+        Coloris({ el: `#${this.element.id}` });
+
+        // set options after initial creation
+        setTimeout(() => {
+            Coloris({ swatches: this.swatchesValue, theme: this.themeValue });
+        });
+    }
+}
+
+window.wagtail.app.register('color', ColorController);
+```
+
+Now we can use this widget in any `FieldPanel` or any `FieldBlock` for StreamFields, it will automatically instantiate the JavaScript to the field's element.
+
+```py
+# blocks.py
+
+# ... other imports
+from django import forms
+from wagtail.blocks import FieldBlock
+
+from .widgets import ColorWidget
+
+
+class ColorBlock(FieldBlock):
+    def __init__(self, *args, **kwargs):
+        swatches = kwargs.pop('swatches', [])
+        theme = kwargs.pop('theme', 'large')
+        self.field = forms.CharField(widget=ColorWidget(swatches=swatches, theme=theme))
+        super().__init__(*args, **kwargs)
+```
+
+```py
+# models.py
+
+# ... other imports
+from django import forms
+from wagtail.admin.panels import FieldPanel
+
+from .blocks import ColorBlock
+from .widgets import ColorWidget
+
+
+BREAD_COLOR_PALETTE = ["#CFAC89", "#C68C5F", "#C47647", "#98644F", "#42332E"]
+
+class BreadPage(Page):
+    body = StreamField([
+        # ...
+        ('color', ColorBlock(swatches=BREAD_COLOR_PALETTE)),
+        # ...
+    ], use_json_field=True)
+    color = models.CharField(blank=True, max_length=50)
+
+    # ... other fields
+
+    content_panels = Page.content_panels + [
+        # ... other panels
+        FieldPanel("body"),
+        FieldPanel("color", widget=ColorWidget(swatches=BREAD_COLOR_PALETTE)),
+    ]
+```
+
 #### Using a build system
 
 You will need ensure your build output is ES6/ES2015 or higher. You can use the exposed global module at `window.StimulusModule` or provide your own using the npm module `@hotwired/stimulus`.