private void buildHiddenSelectorWidget(
      SerialisationContext pSerialisationContext,
      HTMLSerialiser pSerialiser,
      EvaluatedNodeInfoItem pEvalNode,
      List<FieldSelectOption> pSelectOptions) {
    Map<String, Object> lTemplateVars =
        super.getGenericTemplateVars(pSerialisationContext, pSerialiser, pEvalNode);

    // Force the style to be off screen
    lTemplateVars.put("Style", "position: absolute; left: -999em;");

    if (pEvalNode.getSelectorMaxCardinality() > 1) {
      lTemplateVars.put("Multiple", true);
    }

    // Always set size greater than 2, otherwise all fields post back with first value
    lTemplateVars.put("Size", Math.max(2, pSelectOptions.size()));

    List<Map<String, Object>> lOptions = new ArrayList<>();
    OPTION_LOOP:
    for (FieldSelectOption lOption : pSelectOptions) {
      if (lOption.isHistorical() && !lOption.isSelected()) {
        // Skip un-selected historical items
        continue OPTION_LOOP;
      }
      Map<String, Object> lOptionVars = new HashMap<>(3);
      lOptionVars.put("Key", lOption.getDisplayKey());
      lOptionVars.put("Value", lOption.getExternalFieldValue());
      if (lOption.isSelected()) {
        lOptionVars.put("Selected", true);
      }
      if (lOption.isDisabled()) {
        lOptionVars.put("Disabled", true);
      }
      lOptions.add(lOptionVars);
    }
    lTemplateVars.put("Options", lOptions);

    MustacheFragmentBuilder.applyMapToTemplate(
        SELECTOR_MUSTACHE_TEMPLATE, lTemplateVars, pSerialiser.getWriter());
  }
  @Override
  public void buildWidgetInternal(
      SerialisationContext pSerialisationContext,
      HTMLSerialiser pSerialiser,
      EvaluatedNodeInfoItem pEvalNode) {

    FieldMgr lFieldMgr = pEvalNode.getFieldMgr();
    List<FieldSelectOption> lSelectOptions = filteredOptionList(pEvalNode);

    if (!pEvalNode.isPlusWidget() && lFieldMgr.getVisibility() == NodeVisibility.VIEW) {
      SelectorWidgetBuilder.outputReadOnlyOptions(pSerialiser, pEvalNode);
    } else {
      // Create a hidden select element similar to the selector widget
      buildHiddenSelectorWidget(pSerialisationContext, pSerialiser, pEvalNode, lSelectOptions);

      // Find image base URL (the static servlet path)
      RequestURIBuilder lURIBuilder = pSerialisationContext.createURIBuilder();

      // Mapset var name, could be cache key but then possible issues with itemrec
      String lMapsetJSONVariableName =
          lFieldMgr.getExternalFieldName()
              + pEvalNode
                  .getStringAttribute(NodeAttribute.MAPSET, "missing-map-set")
                  .replaceAll("[^a-zA-Z0-9]*", "");
      // Static servlet requires app mnem appended
      String lJSON =
          mapsetToJSON(
              pEvalNode,
              StaticServlet.getURIWithAppMnem(
                  lURIBuilder, pSerialisationContext.getApp().getAppMnem()));
      // Add the JSON to the page
      pSerialisationContext.addConditionalLoadJavascript(
          "var " + lMapsetJSONVariableName + " = " + lJSON + ";");

      // Add a placeholder element
      String lLoadingElementName = "loading" + lFieldMgr.getExternalFieldName();
      pSerialiser.append("<input type=\"text\" id=\"");
      pSerialiser.append(lLoadingElementName);
      pSerialiser.append("\" class=\"tagger tagger-loading\" />");

      // Use the field width as the tagger width if tight field is specified, otherwise tagger
      // should use 100% of the cell
      String lFieldWidth;
      if (pEvalNode.getBooleanAttribute(NodeAttribute.TIGHT_FIELD, false)) {
        lFieldWidth = "$('#" + lLoadingElementName + "').css('width')";
      } else {
        lFieldWidth = "'100%'";
      }

      // Config for the jqueryTagger widget
      JSONObject lTaggerConfig = new JSONObject();
      lTaggerConfig.put("baseURL", lURIBuilder.buildServletURI(StaticServlet.SERVLET_PATH));
      lTaggerConfig.put("imgDownArrow", "/img/tagger-dropdown.png");
      lTaggerConfig.put("imgRemove", "/img/tagger-remove.png");
      lTaggerConfig.put("imgSearch", "/img/tagger-search.png");
      lTaggerConfig.put("fieldWidth", lFieldWidth);

      // Add optinal values to pass to tagger
      lTaggerConfig.putAll(establishExtraParams(pEvalNode));

      // Variable is passed in as a raw variable name to be resolved at runtime
      lTaggerConfig.put("availableTags", new JSONNonEscapedValue(lMapsetJSONVariableName));

      // Pass through lSelectedIdList
      JSONArray lSelectedTags = new JSONArray();
      for (FieldSelectOption lOption : lSelectOptions) {
        if (lOption.isSelected()) {
          lSelectedTags.add(lOption.getExternalFieldValue());
        }
      }
      lTaggerConfig.put("preselectedTags", lSelectedTags);

      boolean lAJAXMapSet = pEvalNode.getMapSet() instanceof JITMapSet;
      if (lAJAXMapSet) {
        lTaggerConfig.put(
            "ajaxURL",
            MapSetWebService.AjaxSearchEndPoint.buildEndPointURI(
                pSerialisationContext.createURIBuilder(),
                pSerialisationContext.getThreadInfoProvider().getThreadId()));
        lTaggerConfig.put(
            "ajaxErrorFunction",
            new JSONNonEscapedValue(
                "function(self, data){self._showMessageSuggestion('The application has experienced an unexpected error, please try again or contact support. Error reference: <strong>' + data.responseJSON.errorDetails.reference + '</strong>', 'error');}"));
      }

      // Add in the JS to construct the tagger for the select
      pSerialisationContext.addConditionalLoadJavascript(
          "$(function(){\n"
              + "  $('#"
              + lFieldMgr.getExternalFieldName()
              + "').tagger("
              + lTaggerConfig.toJSONString()
              + ");\n"
              + "});");
    }
  }