need to copy the answer on custom metric div to another metric preceding the order of the div.

Karanpreet
Tera Contributor

i have a client controller on a widget to copy answer from one custom metric to another metric, but it's not working for yes/no radial buttons metric, for string the value is copying.

 

tried the below script 

api.controller = function ($scope, $timeout) {

  var c = this;

  c.suggestion = '';
  c.message = '';

  function getCtx() {
    // Find the current custom question's field + the survey formModel
    var s = $scope;
    var foundField = null, foundModel = null;

    for (var i = 0; i < 20 && s; i++) {
      if (!foundField && s.field) foundField = s.field;
      if (!foundModel && s.formModel) foundModel = s.formModel;
      if (foundField && foundModel) break;
      s = s.$parent;
    }
    return { field: foundField, formModel: foundModel };
  }

  function getFieldsArray(formModel) {
    if (!formModel) return [];
    var fields = formModel._fields || formModel.fields || formModel.formFields || [];
    if (Array.isArray(fields)) return fields;

    // map -> array
    if (fields && typeof fields === 'object') {
      return Object.keys(fields).map(function (k) { return fields[k]; });
    }
    return [];
  }

  // ---- NEW: utility to normalize suggestion into a boolean ----
  function coerceToBoolean(val) {
    if (val === undefined || val === null) return null;
    // Accept raw boolean
    if (typeof val === 'boolean') return val;

    var s = String(val).trim().toLowerCase();

    // common forms
    if (['true', 't', '1', 'yes', 'y'].indexOf(s) !== -1) return true;
    if (['false', 'f', '0', 'no', 'n'].indexOf(s) !== -1) return false;

    // Heuristic: starts with "yes" or "no"
    if (s.startsWith('yes')) return true;
    if (s.startsWith('no')) return false;

    // Could not decide
    return null;
  }

  // ---- Helper: detect boolean-ish field ----
  function isBooleanField(f) {
    var dt = (f.datatype || '').toLowerCase();
    var t  = (f.type || '').toLowerCase();
    // ServiceNow surveys often use datatype='boolean' or type='boolean'
    // Some widgets render yes/no as 'yesno' or as a checkbox
    return dt === 'boolean' || t === 'boolean' || t === 'yesno' || t === 'checkbox';
  }

  // Find target question = nearest previous NON custom question by order
  function findTargetField(fields, currentField) {
    if (!currentField) return null;

    var currentOrder = parseInt(currentField.order, 10);
    if (isNaN(currentOrder)) currentOrder = 999999;

    var best = null;
    var bestOrder = -1;

    for (var i = 0; i < fields.length; i++) {
      var f = fields[i];
      if (!f) continue;

      var ord = parseInt(f.order, 10);
      if (isNaN(ord)) continue;

      // exclude the current suggestion field itself
      if (f.name === currentField.name) continue;

      // exclude other custom widgets
      if ((f.type || '').toLowerCase() === 'custom') continue;
      if ((f.datatype || '').toLowerCase() === 'custom') continue;

      // nearest previous
      if (ord < currentOrder && ord > bestOrder) {
        best = f;
        bestOrder = ord;
      }
    }
    return best;
  }

  // ---- UPDATED: setFieldValue now handles boolean radios/checkboxes ----
  function setFieldValue(formModel, targetField, value) {
    if (!targetField) return false;

    var wasBoolean = isBooleanField(targetField);
    var finalValue = value;

    if (wasBoolean) {
      var boolVal = coerceToBoolean(value);
      if (boolVal === null) {
        // If we cannot infer boolean from the suggestion, don't set
        return false;
      }
      finalValue = boolVal;

      // Update Angular model (most survey widgets honor stagedValue for booleans)
      targetField.stagedValue = finalValue;
      targetField.value = finalValue; // keep both in sync for safety

      // Try DOM update paths:

      // 1) Checkbox (single input)
      var checkboxEl = document.getElementById('sp_formfield_' + targetField.name);
      if (checkboxEl && checkboxEl.type === 'checkbox') {
        checkboxEl.checked = !!finalValue;
        checkboxEl.dispatchEvent(new Event('input', { bubbles: true }));
        checkboxEl.dispatchEvent(new Event('change', { bubbles: true }));
        return true;
      }

      // 2) Radio group: values might be 'true'/'false', '1'/'0', 'Yes'/'No'
      var radioCandidates = [
        { name: targetField.name, value: finalValue ? 'true'  : 'false' },
        { name: targetField.name, value: finalValue ? '1'     : '0'     },
        { name: targetField.name, value: finalValue ? 'yes'   : 'no'    },
        { name: targetField.name, value: finalValue ? 'Yes'   : 'No'    }
      ];

      var selected = null;
      for (var i = 0; i < radioCandidates.length; i++) {
        var rc = radioCandidates[i];
        var sel = document.querySelector('input[type="radio"][name="' + rc.name + '"][value="' + rc.value + '"]');
        if (sel) { selected = sel; break; }
      }

      if (selected) {
        selected.checked = true;
        // Also uncheck the opposite if needed
        var name = selected.name;
        var group = document.querySelectorAll('input[type="radio"][name="' + name + '"]');
        group.forEach(function (r) {
          if (r !== selected) r.checked = false;
        });
        selected.dispatchEvent(new Event('input', { bubbles: true }));
        selected.dispatchEvent(new Event('change', { bubbles: true }));
        return true;
      }

      // If neither checkbox nor radio found, still return true since the model is set.
      return true;
    }

    // Fallback: non-boolean (string/text/etc.)
    targetField.stagedValue = finalValue;

    // Optional: update DOM if already rendered (nice UX)
    if (targetField.name) {
      var el = document.getElementById('sp_formfield_' + targetField.name);
      if (el) {
        // For selects/choices, set value and trigger change
        if (el.tagName === 'SELECT') {
          el.value = finalValue;
          el.dispatchEvent(new Event('input', { bubbles: true }));
          el.dispatchEvent(new Event('change', { bubbles: true }));
        } else {
          el.value = finalValue;
          el.dispatchEvent(new Event('input', { bubbles: true }));
          el.dispatchEvent(new Event('change', { bubbles: true }));
        }
      }
    }

    return true;
  }

  // Initial load: take suggestion from the current field value (stagedValue/value)
  $timeout(function () {
    var ctx = getCtx();
    if (!ctx.field || !ctx.formModel) {
      c.message = 'Could not find survey context (field/formModel).';
      return;
    }

    // Suggestion comes from THIS suggested field itself
    c.suggestion = ctx.field.stagedValue || ctx.field.value || '';
  }, 0);

  c.accept = function () {
    c.message = '';

    var ctx = getCtx();
    if (!ctx.field || !ctx.formModel) {
      c.message = 'Could not find survey context (field/formModel).';
      return;
    }

    var fields = getFieldsArray(ctx.formModel);
    var targetField = findTargetField(fields, ctx.field);

    if (!targetField) {
      c.message = 'Could not find the target question to populate.';
      return;
    }

    var suggestionValue = ctx.field.stagedValue || ctx.field.value || c.suggestion || '';
    if (!suggestionValue) {
      c.message = 'No suggestion text to apply.';
      return;
    }

    var ok = setFieldValue(ctx.formModel, targetField, suggestionValue);

    // Make sure Angular digests    // Make sure Angular digests the model update
    try { $scope.$applyAsync(); } catch (e) {}

    c.message = ok ? ('Filled target question: ' + (targetField.label || targetField.name)) : 'Failed to fill target question.';
  };

  c.ignore = function () {
    c.message = 'Ignored';
  };

};
 
 can someone help to find the solution
0 REPLIES 0