1

I have a custom field type called signature_field. It invokes a JS file and has its own custom template. I pass a variable to both the JS file and template called $text_area_id. In Drupal if I add multiple fields on the same node with my custom signature_field type my variable is correctly passed to the template for each of the various fields. The JavaScriot code, however, returns only the ID fo the first field added for each of the subsequent fields.

I pass $text_area_id using the following code from the SignatureWidget.php file of a custom module to both the JavaScript code and the template.

namespace Drupal\signature_field\Plugin\Field\FieldWidget;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\ElementInfoManagerInterface;

/**
 * Plugin implementation of the 'field_signature_field_widget' widget.
 *
 * @FieldWidget(
 *   id = "field_signature_field_widget",
 *   module = "signature_field",
 *   label = @Translation("Signature Data"),
 *   field_types = {
 *     "field_signature"
 *   }
 * )
 */
class SignatureWidget extends WidgetBase {

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {

    $value = isset($items[$delta]->value) ? $items[$delta]->value : '';

     // 
     //$sign_thumb = array( '#type'   => 'html_tag',
     //                     '#tag' => 'img',
     //                     '#attributes' => array('src' => '', 'id' => 'signature_thumb_test', 'class'=>array('align-right'), 'width' =>'120px', 'height' => '60px' ));

    $field_name = $items->getName();

    /*function getFieldName() {
      return $field_name;
    }*/

    $text_area_id = 'signature_field_' . $field_name;  

    $file_upload_help = array(
      '#theme' => 'signature',
      '#sign_src' => $value,
      '#text_area_id' => $text_area_id,
    );

    $element += array(
      '#type' => 'textarea',
      '#default_value' => $value,  
      '#attributes' => array(
        'id' => array($text_area_id),
                        ),
      '#attached' => array(
        'library' => array('signature_field/signature_pad'),
                        ),
    );
    $element['#attached']['library'][] = 'signature_field/signature_pad';
    $element['#description'] = \Drupal::service('renderer')->renderPlain($file_upload_help);
    $element['#attached']['drupalSettings']['signature_field']['sign']['id'] = $text_area_id;

    return array('value' => $element);
  }

}

The template code is the following one.

<div class="signature">
    <p>{{text_area_id}}</p>
    <canvas width="450" height="200" id="{{text_area_id}}_canvas" class="sign-canvas">SIGNATURE CANVAS</canvas>
{% if(sign_src != '') %}
    <img src = {{sign_src}} id="{{text_area_id}}_image" class="sign-img"></img>
{% endif %}
     <button type="button" id="{{text_area_id}}_clear_sign" class="button clear" data-action="clear">Clear</button>
</div>

It correctly returns different values based on the field name for each of the fields on the given node. The JavaScript code is the following.

(function ($, window, Drupal, drupalSettings) {

    Drupal.behaviors.signature = {
        attach: function (context, settings) {

            var canvasId = drupalSettings.signature_field.sign.id;
            console.log(canvasId);

            var canvas = document.querySelector('#' + canvasId + "_canvas");
            var signaturePad = new SignaturePad(canvas, {
                minWidth: 0.5,
                maxWidth: 2.5,
                backgroundColor: "rgb(250, 250, 250)",
                penColor: "rgb(66, 133, 244)",
                onEnd:function(){
                    $('#' + canvasId).val(signaturePad.toDataURL());
                }
            });

            $('#' + canvasId).hide();
            $('#' + canvasId + '_clear_sign').click(function(){
                var signaturePad;
                var canvas = document.querySelector('#' + canvasId + "_canvas");
                signaturePad = new SignaturePad(canvas,{
                 penColor: "rgb(66, 133, 244)",
                });
                signaturePad.clear();
                $('#' + canvasId).val('');
            });
            if ($('#' + canvasId + "_image").attr('src')) {
                $('#' + canvasId + "_canvas").hide();
                $('#' + canvasId + '_clear_sign').addClass('chnge-canvas');
            }
            if ($('#' + canvasId + '_clear_sign' + '.chnge-canvas').length) {
                $('#' + canvasId + '_clear_sign' + '.chnge-canvas').click(function(){
                    $('#' + canvasId + '_clear_sign').removeClass('chnge-canvas');
                    $('#' + canvasId + "_image").hide().attr({ 'src':''});
                    $('#' + canvasId + "_canvas").show();
                });
            }

        },
        detach: function (context) {

        }
    };

})(jQuery, this, Drupal, drupalSettings);

The console log -

var canvasId = drupalSettings.signature_field.sign.id;
                console.log(canvasId);

It returns the field name for only the first value of the widget added from the node. It returns it multiple times based on how many times the custom field_type is invoked. How can I get the id for each of the fields in JavaScript? Why does this work different in the template and the JavaScript code?

  • Is getName() returning the same value for each field? – Jaypan Aug 24 '18 at 21:43
  • In the template it correctly returns the name for each separate field. So if I have four fields all using the widget they each have a unique #id derived from getName() in the markup. However when I console log the same variable in the JS file above it returns the same name four times (the first one) from the node. – Shawn Matthews Aug 24 '18 at 22:03
  • Also just to note using kint() returns each name correctly. – Shawn Matthews Aug 24 '18 at 22:13
  • I'm not sure what you'd expect from this? drupalSettings is a global structure and is merged together but you always set the same array key. If you want to support multiple fields then you need to make it an array or give it a unique key in the array – Berdir Aug 26 '18 at 21:55
  • Berdir, do you know if an example of doing this exists anywhere? Like in another module? Your response makes sense to me, but I'm not sure how I would go about implementing that. – Shawn Matthews Aug 26 '18 at 22:11
2
+50

As @Berdir stated, the values are overwritten because the drupalSettings object is merged and your JS file is actually loaded once.
You should pass the IDS as array e.g.:

$element['#attached']['drupalSettings']['signature_field']['sign'][] = $text_area_id;

Then your JS, in the attach function you should loop through them and add the functionality separately, e.g.:

(function ($, window, Drupal, drupalSettings) {

    Drupal.behaviors.signature = {
        attach: function (context, settings) {

            var canvasIds = drupalSettings.signature_field.sign;
            for (var i=0, length = canvasIds.length; i < length; i++){
              this.initiateCanvas(canvasIds[i])
            }
        },
        initiateCanvas: function(canvasId){
          console.log(canvasId); // Yeilds the first id 4 times
          var canvas = document.querySelector('#' + canvasId + "_canvas");
          var signaturePad = new SignaturePad(canvas, {
              minWidth: 0.5,
              maxWidth: 2.5,
              backgroundColor: "rgb(250, 250, 250)",
              penColor: "rgb(66, 133, 244)",
              onEnd:function(){
                  $('#' + canvasId).val(signaturePad.toDataURL());
              }
          });

          $('#' + canvasId).hide();
          $('#' + canvasId + '_clear_sign').click(function(){
              var signaturePad;
              var canvas = document.querySelector('#' + canvasId + "_canvas");
              signaturePad = new SignaturePad(canvas,{
               penColor: "rgb(66, 133, 244)",
              });
              signaturePad.clear();
              $('#' + canvasId).val('');
          });
          if ($('#' + canvasId + "_image").attr('src')) {
              $('#' + canvasId + "_canvas").hide();
              $('#' + canvasId + '_clear_sign').addClass('chnge-canvas');
          }
          if ($('#' + canvasId + '_clear_sign' + '.chnge-canvas').length) {
              $('#' + canvasId + '_clear_sign' + '.chnge-canvas').click(function(){
                  $('#' + canvasId + '_clear_sign').removeClass('chnge-canvas');
                  $('#' + canvasId + "_image").hide().attr({ 'src':''});
                  $('#' + canvasId + "_canvas").show();
              });
          }
        },
        detach: function (context) {

        }
    };

})(jQuery, this, Drupal, drupalSettings);

UPDATE: Ok, I found the issue. We were trying to attached the settings to the $element, it should be attached to the $form, like this:

$form['#attached']['drupalSettings']['signature_field']['sign'][] = $text_area_id;

I tested this and it is working!

  • I made a few corrections to the code your provided above (just some miss-typed variable corrections). This code still logs the same id for each of the four fields (the first one) . For the time being I just hardcoded an array of the field names in the javascript file and everything is working as expected. I don't know where the code is going wrong, but its still yielding the same result as I had before. – Shawn Matthews Aug 29 '18 at 20:25
  • @ShawnMatthews I updated my answer. Please test it this way – Ismail Cherri Aug 30 '18 at 17:33
  • Well that was it.... frustrated I missed that! Thank you everyone for the assistance!! I'll see if I can't get the current signature_field module patched accordingly. – Shawn Matthews Aug 31 '18 at 1:48
-2

Not very familiar with 8 yet, but with 7, the field where the JS is attached should be stored under the context.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.