We've updated the ServiceNow Community Code of Conduct, adding guidelines around AI usage, professionalism, and content violations. Read more

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