Skip to main content
Home  › ... Razor

Customize Edit UI/UX

Tutorial HomeCustomize Edit UX

2sxc Custom Color-Picker Input Field

2sxc 11 makes it very easy to create custom input fields using standard WebComponents. This example shows a color picker using Pickr, a cool JS library.

You can learn how to:

  • Use connector.loadScript(...) to load a js library
  • Use connector.data to get the value and update it
  • How to only update the data if you really change it (so users can cancel the dialog without being asked)
  • How to save null to explicitly not save or reset a value
  • Use disconnectedCallback() to destroy inner objects

String Color-Picker Custom Input Field

Output

This example shows a real string-field uses Pickr to provide a color picker.
Hit this edit button to have a look:

    <p>
      This example shows a real string-field uses Pickr to provide a color picker. <br>
      Hit this edit button to have a look:
    </p>
    @Kit.Toolbar.Empty().New("UiStringColorPickr").AsTag()

    Source Code of ../system/field-string-app-color-pickr/index.js

    /*
      This examples shows a plain JS WebComponent which has has a Pickr color-picker
      Uses the neat Pickr from https://simonwep.github.io/pickr/
      This simple picker has a predefined set of colors and doesn't allow field configuration
    */
    
    // always use an IFFE to ensure you don't put variables in the window scope
    (() => {
    
      const tagName = 'field-string-app-color-pickr';
      const pickrJsCdn = 'https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js';
      const html = `
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/classic.min.css"/>
      <div class="pickr-container"></div>`;
    
      class StringColorPicker extends HTMLElement {
    
        /** connectedCallback() is the standard callback when the component has been attached */
        connectedCallback() {
          this.innerHTML = html;
          // if the window.Pickr doesn't exist yet, load the JS from the CDN () and then do a callback
          this.connector.loadScript('Pickr', pickrJsCdn, () => { this.initPick() });
        }
    
        /** disconnectedCallback() is a standard callback for clean-up */
        disconnectedCallback() {
          if (this.pickr) this.pickr.destroyAndRemove();
        }
    
        /** This is called when the JS is loaded from loadScript - so Pickr is ready */
        initPick() {
          this.pickr = new Pickr({
            el: '.pickr-container',
            theme: 'classic',
            default: this.connector.data.value || null,
            defaultRepresentation: 'HEXA',
            swatches: this.getSwatches(),
            components: {
              // Main components
              preview: true,
              opacity: true,
              hue: true,
    
              // Input / output Options
              interaction: {
                hex: true,
                rgba: true,
                hsla: true,
                hsva: true,
                cmyk: true,
                input: true,
                cancel: true,
                clear: true,
                save: true,
              },
            },
          });
          
          // remember if we're working empty as of now
          this.cleared = !this.connector.data.value;
    
          // bind events for changes etc. to live-update the preview
          this.pickr.on('change', (color, instance) => this.applyColor(instance));
          this.pickr.on('changestop', (instance) => this.applyColor(instance));
          this.pickr.on('swatchselect', (color, instance) => this.applyColor(instance));
    
          this.pickr.on('save', (color,instance) => instance.hide());
          this.pickr.on('hide', (instance) => this.update(instance));
          this.pickr.on('clear', (instance) => {
            this.cleared = true;
            this.update();
          });
        }
    
        /** Update the preview */
        applyColor(instance) {
          this.cleared = false;
          instance.applyColor(true);
        }
    
        /** Update the value */
        update(instance) {
          // if it's still cleared, just save null
          if (this.cleared) {
            return this.updateIfChanged(null);
          }
          // otherwise get the current color
          var color = instance.getColor();
          if (color) color = color.toHEXA().toString();
          this.updateIfChanged(color);
        }
    
        /** Only update the value if it really changed, so form isn't dirty if nothing was set */
        updateIfChanged(value) {
          var data = this.connector.data;
          if (data.value === '' && value == null) return;
          if (data.value === value) return;
          data.update(value);
        }
        
    
        /** Create the default color recommendations for the color picker */
        getSwatches() {
          return [
            'rgba(244, 67, 54, 1)',
            'rgba(233, 30, 99, 0.95)',
            'rgba(156, 39, 176, 0.9)',
            'rgba(103, 58, 183, 0.85)',
            'rgba(63, 81, 181, 0.8)',
            'rgba(33, 150, 243, 0.75)',
            'rgba(3, 169, 244, 0.7)',
            'rgba(0, 188, 212, 0.7)',
            'rgba(0, 150, 136, 0.75)',
            'rgba(76, 175, 80, 0.8)',
            'rgba(139, 195, 74, 0.85)',
            'rgba(205, 220, 57, 0.9)',
            'rgba(255, 235, 59, 0.95)',
            'rgba(255, 193, 7, 1)',
          ]
        }
      }
    
      // Register this web component - if it hasn't been registered yet
      if (!customElements.get(tagName)) customElements.define(tagName, StringColorPicker);
    })();
    Important: We opened permissions that you can experience the edit dialog - so you can save, but it will just create draft data 😉.

    Source Code of this file

    Below you'll see the source code of the file. Note that we're just showing the main part, and hiding some parts of the file which are not relevant for understanding the essentials. Click to expand the code

    @inherits Custom.Hybrid.Razor14
    <!-- unimportant stuff, hidden -->
    
    
    <div @Sys.PageParts.InfoWrapper()>
      @Html.Partial("../shared/DefaultInfoSection.cshtml")
      <div @Sys.PageParts.InfoIntro()>
        <h2>2sxc Custom Color-Picker Input Field</h2>
        <p>
          2sxc 11 makes it very easy to create custom input fields using standard <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components" target="_blank">WebComponents</a>. 
          This example shows a color picker using <a href="https://simonwep.github.io/pickr/" target="_blank">Pickr</a>, a cool JS library. 
        </p>
        <p>You can learn how to:</p>
        <ul>
          <li>Use <code>connector.loadScript(...)</code> to load a js library</li>
          <li>Use <code>connector.data</code> to get the <code>value</code> and update it</li>
          <li>How to only update the data if you really change it (so users can cancel the dialog without being asked)</li>
          <li>How to save <code>null</code> to explicitly not save or reset a value</li>
          <li>Use <code>disconnectedCallback()</code> to destroy inner objects</li>
        </ul>
      </div>
    </div>
    @Html.Partial("../shared/WarnAnonymousIfFeatureIsOff.cshtml")
    </hide>
    <div class="alert alert-success">
      <h2>String Color-Picker Custom Input Field</h2>
    
      <p>
        This example shows a real string-field uses Pickr to provide a color picker. <br>
        Hit this edit button to have a look:
      </p>
      @Kit.Toolbar.Empty().New("UiStringColorPickr").AsTag()
    
    </div>
    
    
    @Html.Partial("warning.cshtml")
    @* Footer *@
    @Html.Partial("../Shared/Layout/FooterWithSource.cshtml", new { Sys = Sys })