Source: src/includes/form/form.elements.js

/**
 * Given a form element, this will return true if access to the element is
 * permitted, false otherwise.
 * @param {Object} element
 * @return {Boolean}
 */
function drupalgap_form_element_access(element) {
  try {
    var access = true;
    if (element.access == false) { access = false; }
    return access;
  }
  catch (error) { console.log('drupalgap_form_element_access - ' + error); }
}

/**
 * Given a form element type, this will return the name of the module that
 * implements the hook_field_widget_form() for the element. Keep in mind for now
 * some of the module names don't exist, and are actually implemented inside
 * the field module. If no module is found, it returns false.
 * @param {String} type
 * @return {String}
 */
function drupalgap_form_element_get_module_name(type) {
  try {
    var module = false;
    switch (type) {
      case 'checkbox':
      case 'radios':
      case 'select':
        module = 'options';
        break;
      case 'image':
        module = 'image';
        break;
    }
    return module;
  }
  catch (error) {
    console.log('drupalgap_form_element_get_module_name - ' + error);
  }
}

/**
 * Given a form element name and the form_id, this generates an html id
 * attribute value to be used in the DOM. An optional third argument is a
 * string language code to use. An optional fourth argument is an integer delta
 * value to use on field elements.
 * @param {String} name
 * @param {String} form_id
 * @return {String}
 */
function drupalgap_form_get_element_id(name, form_id) {
  try {
    if (name == null || name == '') { return ''; }
    var id =
      'edit-' +
      form_id.toLowerCase().replace(/_/g, '-') + '-' +
      name.toLowerCase().replace(/_/g, '-');
    // Any language code to append to the id?
    if (arguments[2]) { id += '-' + arguments[2]; }
    // Any delta value to append to the id?
    if (typeof arguments[3] !== 'undefined') {
      id += '-' + arguments[3] + '-value';
    }
    return id;
  }
  catch (error) { console.log('drupalgap_form_get_element_id - ' + error); }
}

/**
 * Given an element name, this will return the class name to use on the
 * element's container.
 * @param {String} name
 * @return {String}
 */
function drupalgap_form_get_element_container_class(name) {
  try {
    return 'form-item field-name-' + name.replace(/_/g, '-');
  }
  catch (error) {
    console.log('drupalgap_form_get_element_container_class - ' + error);
  }
}

/**
 * Renders all the input elements in a form.
 * @param {Object} form
 * @return {String}
 */
function _drupalgap_form_render_elements(form) {
  try {
    var content = '';
    var content_sorted = '';
    var content_weighted = [];
    // For each form element, if the element objects name property isn't set,
    // set it, then render the element if access is permitted. While rendering
    // the elements, set them aside according to their widget weight so they
    // can be appended to the content string in the correct order later.
    for (var name in form.elements) {
        if (!form.elements.hasOwnProperty(name)) { continue; }
        var element = form.elements[name];
        if (!element.name) { element.name = name; }
        if (drupalgap_form_element_access(element)) {
          if (
            element.is_field &&
            typeof element.field_info_instance.widget.weight !== 'undefined'
          ) {
            var weight = element.field_info_instance.widget.weight;
            while (typeof content_weighted[weight] !== 'undefined') { weight += .1; }
            content_weighted['' + weight] = _drupalgap_form_render_element(form, element);
          }
          else {
            // Extract the bundle. Note, on comments the bundle is prefixed with
            // 'comment_node_' so we need to remove that to correctly map to the
            // potential extra fields data.
            var bundle = null;
            if (form.bundle) {
              bundle = form.bundle;
              if (
                form.entity_type == 'comment' &&
                form.bundle.indexOf('comment_node_') != -1
              ) { bundle = form.bundle.replace('comment_node_', ''); }
            }

            // This is not a field, if it has it's own weight use it, or see if
            // there is a weight in field_info_extra_fields, otherwise just
            // append it to the element content.

            // Elements with a weight defined.
            if (typeof element.weight !== 'undefined') {
              if (content_weighted[element.weight]) {
                var msg = 'WARNING: _drupalgap_form_render_elements - the ' +
                'weight of ' + element.weight + ' for ' + element.name +
                ' is already in use by ' +
                content_weighted[element.weight].name;
                console.log(msg);
                // Just render it.
                var _content = _drupalgap_form_render_element(form, element);
                if (typeof _content !== 'undefined') { content += _content; }
              }
              else {
                content_weighted[element.weight] =
                  _drupalgap_form_render_element(form, element);
              }
            }

            // Extra fields.
            else if (
              form.entity_type && bundle &&
              typeof drupalgap.field_info_extra_fields[bundle][name] !==
                'undefined' &&
              typeof
                drupalgap.field_info_extra_fields[bundle][name].weight !==
                'undefined'
            ) {
              var weight =
                drupalgap.field_info_extra_fields[bundle][name].weight;
              if (content_weighted[weight]) {
                var msg = 'WARNING: _drupalgap_form_render_elements - the ' + 
                'weight of ' + weight + ' for ' + element.name + ' is ' +
                'already in use by ' + content_weighted[weight].name;
                console.log(msg);
                // Just render it.
                var _content = _drupalgap_form_render_element(form, element);
                if (typeof _content !== 'undefined') { content += _content; }
              }
              else {
                content_weighted[weight] =
                  _drupalgap_form_render_element(form, element);
              }
            }

            // No weight, just render it.
            else {
              var _content = _drupalgap_form_render_element(form, element);
              if (typeof _content !== 'undefined') { content += _content; }
            }

          }
        }
    }
    // Prepend the weighted elements to the content.
    if (!empty(content_weighted)) {
      for (var weight in content_weighted) {
        content_sorted += content_weighted[weight] + '\n';
      }
      // Attach sorted content.
      content = content_sorted + '\n' + content;
    }
    // Add any form buttons to the form elements html, if access to the button
    // is permitted.
    if (form.buttons && form.buttons.length != 0) {
      for (var name in form.buttons) {
          if (!form.buttons.hasOwnProperty(name)) { continue; }
          var button = form.buttons[name];
          if (drupalgap_form_element_access(button)) {
            var attributes = {
              type: 'button',
              id: drupalgap_form_get_element_id(name, form.id)
            };
            if (button.attributes) { $.extend(attributes, button.attributes); }
            content += '<button ' + drupalgap_attributes(attributes) + '">' +
              button.title +
            '</button>';
          }
      }
    }
    return content;
  }
  catch (error) { console.log('_drupalgap_form_render_elements - ' + error); }
}

/**
 * Renders an input element for a form.
 * @param {Object} form
 * @param {Object} element
 * @return {String}
 */
function _drupalgap_form_render_element(form, element) {
  try {
    var html = '';

    if (!element) { return html; }

    // Extract the element name.
    var name = element.name;

    // Grab the language.
    var language = language_default();

    // We'll assume the element has no items (e.g. title, nid, vid, etc), unless
    // we determine later that this element is a field, then it'll have items.
    var items = false;

    // If this element is a field, extract the items from the language code and
    // determine what module and hook will handle the items. If the element is
    // not a field, just flatten it into a single item collection and determine
    // which module handles this element type. Keep in mind not all the modules
    // actually exist, and we've placed implementations into the field module.
    var module = false;
    var field_widget_form_function_name = false;
    var field_widget_form_function = false;
    if (element.is_field) {
      items = element[language];
      module = element.field_info_instance.widget.module;
    }
    else {
      items = {0: element};
      module = drupalgap_form_element_get_module_name(element.type);
    }
    if (module) {
      field_widget_form_function_name = module + '_field_widget_form';

      if (function_exists(field_widget_form_function_name)) {
        field_widget_form_function = window[field_widget_form_function_name];
      }
      else {
        console.log(
          'WARNING: _drupalgap_form_render_element() - ' +
          field_widget_form_function_name +
          '() does not exist for the "' + element.type + '" form element!'
        );
      }
    }

    // If there were no items, just return.
    if (!items || items.length == 0) { return html; }

    // Generate default variables.
    var variables = {
      attributes: {}
    };

    // Grab the info instance and info field for the field, then attach them
    // both to the variables object so all theme functions will have access
    // to that data.
    variables.field_info_field = element.field_info_field;
    variables.field_info_instance = element.field_info_instance;

    // Render the element item(s). Remember the final delta value for later.
    var delta = 0;
    var item_html = '';
    var item_label = '';
    var render_item = null;
    for (var delta in items) {
        if (!items.hasOwnProperty(delta)) { continue; }
        var item = items[delta];

        // We'll render the item, unless we prove otherwise.
        render_item = true;

        // Overwrite the variable's attributes id with the item's id.
        variables.attributes.id = item.id;

        // Attach the item as the element onto variables.
        variables.element = item;

        // Create an array for the item's children if it doesn't exist already.
        // This is used by field widget forms to extend form elements.
        if (!items[delta].children) { items[delta].children = []; }

        // Generate the label for field items on delta zero only. Keep in mind
        // rendered labels, with an element title_placeholder set to true,
        // will not be appended to the result html later.
        if (element.is_field && delta == 0) {
          item.title = element.title;
          item_label = theme('form_element_label', {'element': item});
        }

        // If the element's title is set to be a placeholder, set the
        // placeholder attribute equal to the title on the current item, unless
        // someone already set it. If it is a required element, mark it as such.
        if (
          delta == 0 && typeof element.title_placeholder !== 'undefined' &&
          element.title_placeholder &&
          typeof variables.attributes['placeholder'] === 'undefined'
        ) {
          var placeholder = element.title;
          // @TODO show a better required marker for placeholders.
          /*if (element.required) {
            placeholder += ' ' + theme('form_required_marker', { });
          }*/
          variables.attributes['placeholder'] = placeholder;
        }

        // If there wasn't a default value provided, set one. Then set the default value into the variables' attributes,
        // if it wasn't already set, otherwise set it to the item's value.
        if (!item.default_value) { item.default_value = ''; }
        variables.attributes.value = item.default_value;
        if (
            typeof item.value !== 'undefined' &&
            (typeof variables.attributes.value === 'undefined' || empty(variables.attributes.value))
        ) { variables.attributes.value = item.value; }

        // Call the hook_field_widget_form() if necessary. Merge any changes
        // to the item back into this item.
        if (field_widget_form_function) {
          field_widget_form_function.apply(
            null, [
              form,
              null,
              element.field_info_field,
              element.field_info_instance,
              language,
              items,
              delta,
              element
          ]);
          item = $.extend(true, item, items[delta]);
          // If the item type got lost, replace it.
          if (!item.type && element.type) { item.type = element.type; }
        }

        // Merge element attributes into the variables object.
        if (item.options && item.options.attributes) {
          variables.attributes = $.extend(
            true,
            variables.attributes,
            item.options.attributes
          );
        }

        // Render the element item, unless it wasn't supported. Before rendering, clear out any default values so they
        // aren't stale for the next delta item.
        item_html += _drupalgap_form_render_element_item(
          form,
          element,
          variables,
          item
        );
        if (typeof variables.default_value !== 'undefined') { delete variables.default_value; }
        if (typeof variables.default_value_label !== 'undefined') { delete variables.default_value_label; }
        if (typeof variables.value !== 'undefined') { delete variables.value; }
        if (typeof item_html === 'undefined') {
          render_item = false;
          break;
        }
    }

    // Are we skipping the render of the item?
    if (!render_item) { return ''; }

    // Show the 'Add another item' button on unlimited value fields.
    /*if (element.field_info_field &&
      element.field_info_field.cardinality == -1) {
      var add_another_item_variables = {
        text: 'Add another item',
        attributes: {
          'class': 'drupalgap_form_add_another_item',
          onclick:
            "javascript:_drupalgap_form_add_another_item('" +
              form.id + "', '" +
              element.name + "', " +
              delta +
            ')'
        }
      };
      html += theme('button', add_another_item_variables);
    }*/

    // Is this element wrapped? We won't wrap hidden inputs by default, unless
    // someone is overriding it.
    var wrapped = true;
    if (typeof element.wrapped !== 'undefined' && !element.wrapped) {
      wrapped = false;
    }
    if (element.type == 'hidden') {
      wrapped = false;
      if (element.wrapped) { wrapped = true; }
    }

    // If there is an element prefix, place it in the html.
    if (element.prefix) { html += element.prefix; }

    // Open the element container.
    var container_attributes = {
      'class': drupalgap_form_get_element_container_class(name)
    };
    if (wrapped) {
      html += '<div ' + drupalgap_attributes(container_attributes) + '>';
    }

    // Add a label to the element, except submit and hidden elements. Any field
    // labels have already been rendered, other element labels must be manually
    // rendered here. Don't attach the label if the element's title_placeholder
    // is set to true.
    if (element.type != 'submit' && element.type != 'hidden') {
      if (
        typeof element.title_placeholder !== 'undefined' &&
        element.title_placeholder
      ) { /* Skip label for placeholders. */ }
      else {
        if (element.is_field) { html += item_label; }
        else {
          html += theme('form_element_label', {'element': element});
        }
      }
    }

    // Add the item html if it isn't empty. Place the description "before" or "after" the item, exclude it if set
    // to "none".
    if (item_html != '') {
      if (element.description && element.type != 'hidden') {
        var descriptionDisplay = element.description_display ? element.description_display : 'after';
        var descriptionHtml = '<div class="description">' + t(element.description) + '</div>';
        switch (descriptionDisplay) {
          case 'before': html += descriptionHtml + item_html; break;
          case 'after': html += item_html + descriptionHtml; break;
          case 'none': html += item_html; break;
        }
      }
      else { html += item_html; }
    }

    // Close the element container.
    if (wrapped) { html += '</div>'; }

    // If there is an element suffix, place it in the html.
    if (element.suffix) { html += element.suffix; }

    // Return the element html.
    return html;

  }
  catch (error) { console.log('_drupalgap_form_render_element - ' + error); }
}

/**
 * Given a form, an element, the variables for a theme function, and the element
 * item, this will return the html rendering of the element item.
 * @param {Object} form
 * @param {Object} element
 * @param {Object} variables
 * @param {Object} item
 * @return {*}
 */
function _drupalgap_form_render_element_item(form, element, variables, item) {
  try {
    var html = '';
    // Depending on the element type, if necessary, adjust the variables and/or
    // theme function to be used, then render the element by calling its theme
    // function.
    // @TODO - this block of code should be moved into their respective
    // implementations of hook_field_widget_form().
    switch (item.type) {
      case 'text':
        item.type = 'textfield';
        break;
      case 'list_text':
      case 'list_float':
      case 'list_integer':
        item.type = 'select';
        break;
    }

    // Set the theme function.
    var theme_function = item.type;

    // If the element is disabled, add the 'disabled' attribute.
    if (element.disabled) { variables.attributes.disabled = ''; }

    // Make any preprocess modifications to the elements so they will map
    // cleanly to their theme function.
    // @todo A hook_field_widget_form() should be used instead here.
    if (item.type == 'submit') {
      variables.attributes.onclick =
        '_drupalgap_form_submit(\'' + form.id + '\');';
      if (!variables.attributes['data-theme']) {
        variables.attributes['data-theme'] = 'b';
      }
      if (typeof variables.attributes.type === 'undefined') {
        variables.attributes.type = 'button';
      }
      if (typeof variables.attributes['class'] === 'undefined') {
        variables.attributes['class'] = '';
      }
      variables.attributes['class'] += ' dg_form_submit_button ';
    }

    // Merge the item into variables.
    $.extend(true, variables, item);

    // If a value isn't set on variables, try to set it with the default value
    // on the item.
    if (typeof variables.value === 'undefined' || variables.value == null) {
      if (typeof item.default_value !== 'undefined') {
        variables.value = item.default_value;
      }
    }

    // Run the item through the theme system if a theme function exists, or try
    // to use the item markup, or let the user know the field isn't supported.
    if (function_exists('theme_' + theme_function)) {
      html += theme(theme_function, variables);
    }
    else {
      if (item.markup || item.markup == '') { html += item.markup; }
      else {
        // @todo - the reason for this warning sometimes happens because the
        // item.type is lost with $.extend in _drupalgap_form_render_element().
        // @update - if an item doesn't have a type, it gets set by the parent
        // element, so we should now always have a type available here.
        var msg = 'Field ' + item.type + ' not supported.';
        console.log('WARNING: _drupalgap_form_render_element_item() - ' + msg);
        dpm(item);
        return null;
      }
    }

    // Render any item children. If the child has markup, just use the html,
    // otherwise run the child through theme().
    if (item.children && item.children.length > 0) {
      for (var i = 0; i < item.children.length; i++) {
        if (item.children[i].markup) { html += item.children[i].markup; }
        else if (item.children[i].type || item.children[i].theme) {
          var theme_type = item.children[i].type;
          if (!theme_type) { theme_type = item.children[i].theme; }
          // Is there a title for a label?
          if (item.children[i].title) {
            html += theme('form_element_label', {
                element: item.children[i]
            });
          }
          // Render the child with the theme system.
          if (item.children[i].prefix) { html += item.children[i].prefix; }
          html += theme(theme_type, item.children[i]);
          if (item.children[i].suffix) { html += item.children[i].suffix; }
        }
        else {
          console.log(
            'WARNING: _drupalgap_form_render_element_item - ' +
            'failed to render child ' + i + ' for ' + element.name
          );
        }
      }
    }

    return html;
  }
  catch (error) {
    console.log('_drupalgap_form_render_element_item - ' + error);
  }
}

/**
 * Given an element name, the form, a language code and a delta value, this
 * will return default values that can be used to place an item element into a
 * Forms API object.
 * @param {String} name
 * @param {Object} form
 * @param {String} language
 * @param {Number} delta
 * @return {Object}
 */
function drupalgap_form_element_item_create(name, form, language, delta) {
  try {
    // Generate the id for this element field item and set it and
    // some default options onto the element item.
    var id = drupalgap_form_get_element_id(name, form.id, language, delta);
    return {
      id: id,
      options: {
        attributes: {
          id: id
        }
      },
      required: form.elements[name].required
    };
  }
  catch (error) {
    console.log('drupalgap_form_element_item_create - ' + error);
  }
}

/**
 *
 * @param {Object} form
 * @param {Object} form_state
 * @param {Object} element
 * @param {String} language
 * @param {Number} delta
 * @return {Array}
 */
function _drupalgap_form_element_items_widget_arguments(form, form_state,
  element, language, delta) {
  try {
    var widget_arguments = [];
    widget_arguments.push(form); // form
    widget_arguments.push(form_state); // form state
    widget_arguments.push(element.field_info_field); // field
    widget_arguments.push(element.field_info_instance); // instance
    widget_arguments.push(language); // language
    widget_arguments.push(form.elements[element.name][language]); // items
    widget_arguments.push(delta); // delta
    widget_arguments.push(element); // element
    return widget_arguments;
  }
  catch (error) {
    console.log('_drupalgap_form_element_items_widget_arguments - ' + error);
  }
}