/*jslint indent: 2, maxlen: 80, nomen: true, sloppy: true, todo: true */
/*global console, window, jIO, complex_queries, FormData, RSVP, document,
 jQuery, i18n, Recaptcha, XMLHttpRequest, alert, Modernizr */

(function (window, document, $) {
  "use strict";

  var factory, map, storage, app, util, flux;

  /* ====================================================================== */
  /*                               FACTORY                                  */
  /* ====================================================================== */
  /**
   * object containing UI generator methods
   */
  factory = {
    "widget": {},
    "util": {}
  };

  /* ********************************************************************** */
  /*                               Element                                  */
  /* ********************************************************************** */
  /**
   * Generate an HTML element
   * Full spec (element) example (with defaults):
   * {
   *   "type": element (excluding input/select,
   *   "direct": { directly settable parameters (eg. id, value) },
   *   "attributes": { directly settable attributes (eg. data-role) },
   *   "logic": { parameters/attributes requiring validation }
   *   "children": []
   * }
   * @method element
   * @param {object} spec Configuration for a form element
   * @return HTML fragment!
   **/
  // TODO: optgroup support!
  factory.element = function (spec) {
    var element, setter, attribute, logic, item, i, mock, recurse, pic;

    // element
    element = document.createElement(spec.type);
    logic = spec.logic;

    // set without question!
    for (setter in spec.direct) {
      if (spec.direct.hasOwnProperty(setter)) {
        element[setter] = spec.direct[setter];
      }
    }
    for (attribute in spec.attributes) {
      if (spec.attributes.hasOwnProperty(attribute)) {
        element.setAttribute(attribute, spec.attributes[attribute]);
      }
    }

    // requires logic
    for (item in logic) {
      if (logic.hasOwnProperty(item)) {
        mock = item.slice(0, 5) === "data-" ? "data-" : item;
        switch (mock) {
        case "disabled":
        case "selected":
          if (logic[item]) {
            element.setAttribute(item, item);
          }
          break;
        case "id":
        case "href":
        case "rows":
        case "cols":
        case "name":
        case "media":
        case "value":
        case "data-":
        case "role":
        case "type":
        case "rel":
        case "alt":
        case "src":
        case "readonly":
        case "size":
        case "colspan":
        case "rowspan":
          if (logic[item]) {
            element.setAttribute(item, logic[item]);
          }
          break;
        case "text":
          if (logic.text) {
            element.appendChild(document.createTextNode(logic.text));
          }
          break;
        case "img":
          pic = logic.img;
          if (pic) {
            element.appendChild(factory.element({
              "type": "img",
              "direct": {"src": pic.href},
              "logic": {
                "data-i18n": pic.alt_i18n ? ("[alt]" + pic.alt_i18n) : null,
                "alt": pic.alt || ""
              }
            }));
          }
          break;
        case "options":
          if (logic.options) {
            for (i = 0; i < logic.options.length; i += 1) {
              recurse = logic.options[i];
              if (recurse) {
                element.appendChild(factory.element({
                  "type": "option",
                  "direct": {
                    "className": (recurse.text_i18n ? "translate" : ""),
                    "value": recurse.value
                  },
                  "attributes": {"data-i18n": recurse.text_i18n || ""},
                  "logic": {
                    "text": recurse.text,
                    "selected": recurse.selected,
                    "disabled": recurse.disabled
                  }
                }));
              }
            }
          }
          break;
        case "extra":
          if (typeof logic.extra !== "string") {
            for (recurse in logic.extra) {
              if (logic.extra.hasOwnProperty(recurse)) {
                element.setAttribute(recurse, logic.extra[recurse]);
              }
            }
          }
          break;
        }
      }
    }

    return element;
  };

  /* ********************************************************************** */
  /*                            Factory Widget                              */
  /* ********************************************************************** */

  /* ********************************************************************** */
  /*                         JQM Form Element                               */
  /* ********************************************************************** */
  /**
   * Generate a JQM form element (input/select)
   * Full spec (element) example (with defaults):
   * {
   *   "type": "input|select",
   *   "direct": { directly settable parameters (eg. id, value) },
   *   "attributes": { directly settable attributes (eg. data-role) },
   *   "logic": { parameters/attributes requiring validation }
   *   "wrap": "true|false|undefined",
   *   "position": "true(first)|false(last)|undefined),
   *   "label": "true|false"
   * }
   * Generate a form
   * @method formElement
   * @param {object} spec Configuration for a form element
   * @return HTML fragment!
   **/
  // TODO: this should be a generator of widget.form, no?
  // TODO: mini? shadow? corners?
  // TODO: slider/custom-select/flip
  // TODO: add label et all to span-only?
  // TODO: we should not have to clear-button remove here, do in mapFormField
  // TODO: needs validation messages
  // TODO: could have children = options!
  factory.widget.formElement = function (spec) {
    var wrapper, container, wrap_in_container, element_reverse,
      label_inside, label_target, element_target, action, clear,
      theme, icon_string, input_type, need_text_node, container_class_list,
      label_class_list, index, disabled, active, text, addLabel, readonly,
      mask_set, mask, star, hidden_field, icon, no_validate, has_opts,
      push_label, no_text, label_set;

    // exit early with a span tag
    // TODO: make this with label et al
    if (spec.type === "span") {
      wrapper = factory.element(spec);
      return {
        "fragment": wrapper,
        "child_selector": wrapper
      };
    }

    // method to generate label
    addLabel = function (config, label_class_list) {
      // [r]equired class
      star = util.testForString("equire", config.direct.className, true);
      return factory.element({
        "type": "label",
        "direct": {
          "className": label_class_list + " translate" +
              (star ? " required_label " : " ") +
            ((config.label === undefined && config.logic.text === "") ?
                " ui-hidden-accessible" : "")
        },
        "attributes": {
          "for": config.direct.id || util.uuid()
        },
        "logic": {
          "text": config.logic.label || "",
          "data-i18n": config.logic.label_i18n || null
        }
      });
    };

    // bear with me...

    // add missing logic
    spec.logic = spec.logic || {};
    spec.attributes = spec.attributes || {};

    // enhanced!
    spec.attributes["data-enhanced"] = true;

    // class string: theme
    theme = spec.attributes["data-theme"];


    // class string: first/last element in button groups (default = undefined)
    index = "";
    switch (spec.position) {
    case null:
      index = " ui-first-child ui-last-child ";
      break;
    case true:
      index = " ui-first-child ";
      break;
    case false:
      index = " ui-last-child ";
      break;
    }

    // class string: form field attributes
    disabled = "";
    readonly = "";
    active = "off";
    mask = "";

    if (spec.direct.checked) {
      active = "on";
    }
    if (spec.attributes.disabled) {
      disabled = " ui-disabled";
      mask_set = true;
    }
    if (spec.attributes.readonly) {
      readonly = " ui-readonly";
      mask_set = true;
    }

    // hidden field
    if (spec.attributes.type === "hidden") {
      hidden_field = true;
      delete spec.logic.clear;
    }

    // class string: disabled/readonly mask
    if (mask_set) {
      mask = " ui-mask-input";
    }

    // optionally wrap input in a div-fieldcontain
    // NOTE: route is for custom HTML elements... service_instance_status
    if ((!spec.logic.route && (spec.logic.wrap !== true && spec.wrap !== true))
        || hidden_field) {
      wrapper = document.createDocumentFragment();
    } else {
      wrapper = factory.element({
        "type": "div",
        "direct": {"className": "ui-fieldcontain translate"},
        "attributes": {
          "title": spec.logic.title || "",
          "data-i18n": "[title]" + (spec.logic.title_i18n || "")
        }
      });
    }

    // class string: textarea
    if (spec.type === "textarea") {
      spec.direct.className += " ui-input-text ui-shadow-inset " +
        "ui-body-inherit ui-corner-all" + disabled + readonly;
    }

    // class string: action button
    if (spec.logic.action) {
      action = " ui-input-has-action ui-input-search-no-pseudo";
      spec.direct.className += " action ";
      spec.attributes["data-action-btn"] = "true";
    }

    // class string: clear button
    if (mask_set === undefined) {
      if (spec.logic.clear) {
        clear = " ui-input-has-clear";
        spec.attributes["data-clear-btn"] = "true";
      }
    }

    // label position (before/after element) and class string
    container_class_list = spec.logic.wrapper_class_list || "";
    label_class_list = "";

    // TODO: no, no only because of custom elements having to go through here
    if (!spec.logic.route) {
      text = "text";
      has_opts = spec.logic.options;
      input_type = spec.attributes.type;
      icon = spec.attributes["data-icon"];

      if (input_type) {
        switch (input_type) {
        case "radio":
          // TODO: not good, because a single radio/check may need validate
          // messages. But can't be determined here if this element is in
          // a group
          no_validate = true;
          no_text = spec.attributes["data-iconpos"];
          container_class_list = "ui-" + spec.attributes.type;
          label_inside = true;
          push_label = (has_opts && has_opts.length > 0);
          label_class_list = "ui-btn ui-corner-all ui-btn-inherit " +
            " ui-icon-" + (icon || "radio-off") + " ui-radio-" +
            active + index + disabled + readonly +
              (no_text ? " ui-btn-icon-" + no_text : "");
          spec.attributes["data-cacheval"] = true;
          delete spec.logic.options;
          break;
        case "checkbox":
          no_validate = true;
          icon_string = factory.util.classes(spec, "checkbox");
          container_class_list = "ui-" + spec.attributes.type;
          label_inside = true;
          no_text = spec.attributes["data-iconpos"];
          push_label = (has_opts && has_opts.length > 0);
          label_class_list = "ui-btn ui-corner-all ui-btn-inherit " +
            icon_string + " ui-checkbox-" + active + index + disabled +
              readonly + (no_text ? " ui-btn-icon-" + no_text : "");
          spec.attributes["data-cacheval"] = false;
          delete spec.logic.options;
          break;
        case "button":
        case "submit":
        case "reset":
          if (icon) {
            icon_string = factory.util.classes(spec, icon, true);
          }
          container_class_list += "ui-btn ui-input-btn " + icon_string;
          need_text_node = true;
          no_validate = true;
          break;
          // covers all JQM text input types and excludes select/textarea...
        default:
          if (input_type !== "hidden") {
            if (input_type === "search") {
              spec.attributes["data-type"] = "search";
              text = "search";
            }
            // disable IOS auto functionality (WARNING: fails W3C validation)
            //spec.attributes.autocapitalize = "none";
            //spec.attributes.autocorrect = "none";

            container_class_list = "ui-input-" + text;
          }
          break;
        }
      }

      // overwrites for textarea elements
      if (spec.type === "textarea") {
        spec.direct.className += mask;
        //delete spec.attributes.type;
      }

      // class string: assemble
      container_class_list += (no_validate ? "" :
          " ui-corner-all ui-shadow-inset") +
        disabled + readonly + (action || "") + (clear || "") +
          (theme ? (" ui-bar-" + theme + " ") : " ui-body-inherit ");
    }

    // NOTE: time to make elements - needs shuffling to fit all variants

    // container
    if (spec.type === "textarea" || hidden_field || spec.logic.route) {
      container = wrapper;
    } else {
      wrap_in_container = true;
    }

    // select element (NOTE: JQM still enhances and copies all classes to
    // span and select...)
    if (spec.type === "select") {
      container_class_list = "ui-" + spec.type;
      icon_string = spec.attributes["data-icon"];

      element_target = factory.element({
        "type": "div",
        "direct": {
          "id": spec.direct.id + "-button",
          "className": " ui-btn ui-corner-all ui-shadow " +
              (spec.logic.wrapper_class_list || "") + mask +
                (icon_string ? (" ui-icon-" + icon_string + " ui-btn-icon-" +
                  (spec.attributes["data-iconpos"] || "left")) :
                  (" ui-icon-caret-down ui-btn-icon-right"))
        },
        "attributes": {"data-enhanced": "true"}
      });
      element_reverse = true;
    }

    // run "queued" commands (need container_class_list to be set here)
    if (wrap_in_container) {
      container = factory.element({
        "type": "div",
        "direct": {"className": container_class_list + mask}
      });
    }

    // set label target
    if (push_label || label_inside) {
      label_target = container;
    } else {
      label_target = wrapper;
    }

    // NOTE: always label for validity, "no label" means "hide it" not "skip"!
    // NOTE: no label for submit/reset because we don't need an id/name
    // NOTE: for checkboxRadio validation, the label needs to be AFTER
    // NOTE: hidden fields & security fields get no la
    // the input, otherwise "invalid" CSS sibling selector will not work.
    // This saves doing "invalid" handler with javascript
    if (spec.logic.add_label !== false && ((need_text_node === undefined &&
        (push_label || !label_inside) && !hidden_field) || spec.logic.route)) {
      label_set = true;
      label_target.appendChild(addLabel(spec, label_class_list));
    }

    // add <span> which contains JQM current select
    if (element_reverse) {
      element_target.appendChild(factory.element({"type": "span"}));
    } else {
      element_target = container;
    }

    // add text node containing JQM button values
    if (need_text_node) {
      element_target.appendChild(document.createTextNode(spec.direct.value));
    }

    // alas... make the ELEMENT itself
    element_target.appendChild(factory.element(spec));

    // checkbox radio need label after input
    if (label_inside && !label_set) {
      label_target.appendChild(addLabel(spec, label_class_list));
    }

    // add invalid validation messages
    // TODO: "wrong" is wrong - but don't put any text references here
    // all text references should be somewhere in config!
    if (!no_validate && !spec.logic.route) {
      element_target.appendChild(factory.element({
        "type": "span",
        "direct": {"className": "ui-invalid-label translate"},
        "attributes": {"data-i18n": "validation_dict.required_field"}
      }));
    }

    // reassemble select elements and add spans
    if (element_reverse) {
      container.appendChild(element_target);
    }

    // add clear button
    if (spec.logic.clear) {
      container.appendChild(factory.element({
        "type": "a",
        "direct": {
          "title": "Clear",
          "className": "ui-input-clear ui-btn ui-icon-delete translate " +
            "ui-btn-icon-notext ui-corner-all ui-input-clear-hidden " +
            (theme ? ("ui-btn-" + theme) : ""),
          "href": "#"
        },
        "attributes": {"data-i18n": "global.actions.clear", "tabindex": "-1"},
        "logic": {"text": "Clear"}
      }));
    }

    // add action button
    // TODO: should this be a submit via option?
    if (spec.logic.action) {
      container.appendChild(factory.element({
        "type": "a",
        "direct": {
          "title": spec.logic.action.text || "Search",
          "className": "ui-input-action ui-btn ui-icon-" +
            (spec.logic.action.icon || "search") +
              " ui-btn-icon-notext ui-corner-all translate " +
                (theme ? ("ui-btn-" + theme) : ""),
          "href": "#"
        },
        "attributes": {
          "data-i18n": ("[title]" + spec.logic.action.text_i18n + ";" +
            spec.logic.action.text_i18n) ||
              "[title]global_dict.search;global_dict.search",
          "tabindex": "-1",
          "data-action": spec.logic.action.action || "search"
        },
        "logic": {"text": spec.logic.action.text || "Search"}
      }));
    }

    // wrap up
    if (wrap_in_container) {
      wrapper.appendChild(container);
    } else {
      wrapper = container;
    }
    return wrapper;
  };

  /* ********************************************************************** */
  /*                                JQM Page                                */
  /* ********************************************************************** */
  /**
   * Generate a JQM page
   * Full spec (property_dict) example (with defaults):
   * {
   *   "generate": "widget",
   *   "type": "page",
   *   "property_dict" : {
   *     "title": "",
   *     "title_i18n": null,
   *     "theme": null,
   *     "fix_footer": null,
   *     "fix_header": null
   *     "create": null (added automatically)
   *     "data_url": null (added automatically)
   *     "fragment_list": [] (added automatically)
   *   },
   *   "children": []
   * }
   * @method page
   * @param {object} spec JSON configuration
   * @return {object} object including fragment, child-selector, placeholder
   **/
  factory.widget.page = function (spec) {
    var container, wrap, target, encoded;

    container = util.getPage(spec.data_url) ||
        document.getElementById(spec.data_url);
    encoded = window.encodeURIComponent(spec.data_url.split("?")[0]);

    // new page
    if (container === null || spec.create === true) {
      container = factory.element({
        "type": "div",
        "direct": {
          "className": " ui-page " + ("ui-page-theme-" +
              spec.theme || "") + " " +
                (spec.fix_header ? " ui-page-header-fixed" : " ") +
                  (spec.fix_footer ? " ui-page-footer-fixed" : " ")
        },
        "attributes": {
          "data-role": "page",
          "data-url": encoded,
          "data-external-page": true,
          "tabindex": 0,
          "data-enhanced": true
        }
      });

      // content wrap
      wrap = factory.element({
        "type": "div",
        "direct": {"className": "ui-content"}
      });
      wrap.setAttribute("data-bound", true);
      wrap.appendChild(factory.crumbs({
        "children": spec.fragment_list,
        "home": true,
        "data-url": spec.data_url
      }));
      container.appendChild(wrap);
      target = container.querySelector("div.ui-content");

    // page update
    } else {
      target = container.querySelector("[data-update]") ||
          container.querySelector("div.ui-content") ||
              container;
    }

    // return new page and/or pointer to gadget content container
    return {
      "fragment": container,
      "child_selector": target,
      "is_page": true
    };
  };

  /* ********************************************************************** */
  /*                          JQM Collapsible-Set                           */
  /* ********************************************************************** */
  // TODO: BROKEN!
  /**
   * Generate a collapsible set
   * Full spec (property_dict) example (with defaults):
   * {
   *   "generate": "widget",
   *   "type": "collapsible",
   *   "property_dict": {
   *     "class_list": "",
   *     "set": false,
   *     "direction": horizontal||null,
   *     "inset": null,
   *     "corners": null,
   *     "theme": null,
   *     "content_theme": null,
   *     "mini": null,
   *     "collapsed_icon": null,
   *     "expanded_icon": null,
   *     "iconpos": null,
   *     "form": null
   *     "form_config": {
   *       "action": null,
   *       "method": null,
   *       "secure": null
   *     }
   *   },
   *   "children": []
   * }
   * @method collapsibleset
   * @param {object} spec JSON configuration
   * @return {object} object including fragment, child-selector, placeholder
   **/
  factory.widget.collapsibleset = function (spec) {
    var container, form, obj, prop;

    // TODO: why spec.set, BS?
    if (spec.set) {
      container = factory.element({
        "type": "div",
        "direct": {
          "className": (spec.class_list || "") + "ui-collapsible-set " +
              ((spec.corners || !spec.inset) ? "ui-corner-all " : "") +
                ("ui-group-theme-" + spec.theme || "inherit")
        },
        "attributes": {
          "data-role": "collapsibleset",
          "data-enhanced": true
        },
        "logic": {
          "data-corners": spec.corners,
          "data-inset": spec.inset,
          "data-mini": spec.mini,
          "data-collapsed-icon": spec.collapsed_icon,
          "data-expanded-icon": spec.expanded_icon,
          "data-theme": spec.theme,
          "data-content-theme": spec.content_theme
        }
      });
    } else {
      container = document.createDocumentFragment;
    }

    // TODO: what does this do here?
    if (spec.form) {
      form = factory.widget.form(spec.form_config);
      form.appendChild(container);
    }

    obj = {};

    // TODO: find way to async inherit, this is not a goodway
    // set properties for child collapsible
    for (prop in spec) {
      if (spec.hasOwnProperty(prop)) {
        switch (prop) {
        case "direction":
        case "class_list":
        case "id":
          break;
        default:
          obj[prop] = spec[prop];
          break;
        }
      }
    }

    return {
      "fragment": form || container,
      "child_selector": container
    };
  };


  /* ********************************************************************** */
  /*                          JQM Collapsible                               */
  /* ********************************************************************** */
  /**
   * Generate a collapsible (full spec with default below)
   *     {
   *      "generate": "collapsible",
   *      "property_dict": {
   *        "href": "#",
   *        "id": null,
   *        "class_list": null,
   *        "has_form": null,
   *        "inset": true,
   *        "theme": null,
   *        "content_theme": null,
   *        "corners": null,
   *        "collapsed": false,
   *        "collapsed_icon": "plus",
   *        "expanded_icon": "minus",
   *        "iconpos": "left",
   *        "mini": null,
   *        "text": "Collapsible Heading",
   *        "text_i18n": null,
   *        "title": "Click to expand",
   *        "title_i18n": null,
   *        "dynamic": null
   *      },
   *      "children": []
   *     }
   * @method collapsible
   * @param {object} spec JSON configuration
   * @return {object} object including fragment, child-selector, placeholder
   **/
  // TODO: add dynamic
  factory.widget.collapsible = function (spec) {
    var container, header, target, link, form, collapsed, translate;

    translate = spec.text_i18n ? "translate " : " ";
    collapsed = spec.collapsed === false;

    container = factory.element({
      "type": spec.form ? "fieldset" : "div",
      "direct": {
        "className": "ui-collapsible " + (spec.mini ? "ui-mini " : "") +
          (!spec.inset ? "" : "ui-collapsible-inset ui-corner-all ") +
            (spec.content_theme ? "ui-collapsible-themed-content " : " ") +
              (collapsed ? " " : "ui-collapsible-collapsed ") +
                (spec.class_list || "")
      },
      "attributes": {
        "data-role": "collapsible",
        "data-enhanced": true
      },
      "logic": {
        "data-inset": spec.inset === false ? false : null,
        "data-theme": spec.theme,
        "data-content-theme": spec.content_theme,
        "data-collapsed": spec.collapsed === false ? false : null,
        "data-mini": spec.mini,
        "data-collapsed-icon": spec.collapsed_icon,
        "data-expanded-icon": spec.expanded_icon,
        "data-iconpos": spec.iconpos
      }
    });

    header = factory.element({
      "type": spec.form ? "div" : "h1",
      "direct": {
        "className": "ui-collapsible-heading " +
            (collapsed ? " " : "ui-collapsible-heading-collapsed")
      },
      "logic": {
        //pfff (WARNING: fails W3C validation)
        //"role": spec.form ? "heading" : null
      }
    });

    link = factory.element({
      "type": "a",
      "direct": {
        "href": spec.href || "#",
        "className": "ui-collapsible-heading-toggle ui-btn " +
            "ui-btn-icon-" + (spec.iconpos || "left") + " " +
              "ui-icon-" + (!spec.collapsed ?
                (spec.collapsed_icon || "minus") :
                  (spec.expanded_icon || "plus")) + " " + translate
      },
      "logic": {
        "data-i18n": spec.text_i18n ? ("[node]" + spec.text_i18n) : null,
        "data-ajax": spec.href ? "false" : null
      }
    });
    link.appendChild(document.createTextNode(spec.text || "\u00A0"));
    link.appendChild(factory.element({
      "type": "span",
      "direct": {
        "className": "ui-collapsible-heading-status " + translate
      },
      "logic": {
        "text": spec.title || "Click to expand contents",
        "data-i18n": (i18n ? "global.collapsible_hint" : null)
      }
    }));

    header.appendChild(link);

    target = factory.element({
      "type": "div",
      "direct": {
        "className": "ui-collapsible-content " + (spec.collapsed === false ?
            "" : "ui-collapsible-content-collapsed ") + "ui-body-" +
              (spec.content_theme || "inherit")
      },
      "attributes": {
        "aria-hidden": spec.collapsed === false ? false : true
      },
      "logic": {}
    });

    // TODO?
    if (spec.form) {
      target.appendChild(factory.element({
        "type": "div",
        "logic": {"text": "<!-- placeholder for legend -->"}
      }));
      form = factory.widget.form(spec.form_config);
      form.child_selector.appendChild(container);
    }

    container.appendChild(header);
    container.appendChild(target);

    return {
      "fragment": form || container,
      "child_selector": container.querySelector("div.ui-collapsible-content")
    };
  };

  /* ********************************************************************** */
  /*                            JQM Header                                  */
  /* ********************************************************************** */
  /**
   * Generates JQM header.
   * Full spec (property_dict) example (with defaults):
   * {
   *   "generate": "widget",
   *   "type": "Header",
   *   "property_dict": {
   *     "class_list": "",
   *     "theme": "",
   *     "id": null,
   *     "title": "",
   *     "title_i18n":"",
   *     "fixed": true,
   *     "add_content": 1,
   *     "image": {
   *       "src": null,
   *       "alt": null,
   *       "alt_i18n": null
   *     }
   *   },
   *   "children": [],
   *   }
   * @method header
   * @param {object} spec JSON configuration
   * @return {object} object including fragment, child-selector, placeholder
   */
  // TODO: too complicated and too much custom stuff passed back
  // This cannot be so difficult...
  factory.widget.header = function (spec) {
    var container, id, target, set_img, img, pic;

    container = document.createDocumentFragment();
    id = spec.id || ((spec.data_url || "global") + "-header");
    set_img = spec.image;
    img = spec.image || {};

    // button group wrappers and title (inserted before last wrapper!)
    target = function (new_spec) {
      var position, drop_content, fragment, count, config, i, len;

      count = new_spec.j;
      config = new_spec.config;
      fragment = document.createDocumentFragment();
      position = (config.section_list || ["first", "last"])[count];
      drop_content = config.add_content === 0 ? 0 : 1;

      if (count === drop_content) {
        if (config && config.img) {
          fragment.appendChild(factory.element({
            "type": "img",
            "direct": {"src": config.src, "className": "ui-title-logo"},
            "attributes": {"alt": config.title},
            "logic": {
              "data-i18n": config.title_i18n ?
                  ("[alt]" + config.title_i18n) : null
            }
          }));
        // TODO: separate method!
        } else if (config.src_set) {
          // NOTE: responsive <picture> element
          pic = factory.element({"type": "picture"});
          len = config.src_set.length;

          for (i = 0; i < len; i += 1) {
            pic.appendChild(factory.element({
              "type": "source",
              "attributes": {"srcset": config.src_set[i].src},
              "logic": {"media": config.src_set[i].media || null}
            }));
          }

          pic.appendChild(factory.element({
            "type": "img",
            "direct": {"className": "ui-title-logo", "alt": config.alt},
            "attributes": {"srcset": config.src_set[len - 1].srcset}
          }));
//           source = factory.element({
//             "type": "img",
//             "direct": {"alt": config.alt},
//             "attributes": {"srcset": config.src_set[len - 1].srcset}
//           });
          fragment.appendChild(pic);
        } else {
          fragment.appendChild(factory.element({
            "type": "h1",
            "direct": {"className": "translate ui-title"},
            "attributes": {
              "data-i18n": config.title_i18n || "",
              // WARNING Fails W3C
              //"role": "heading",
              "aria-level": "1"
            },
            "logic": {"text": config.title || "\u00A0"}
          }));
        }
      }
      if (position) {
        fragment.appendChild(factory.element({
          "type": "div",
          "direct": {"className": "ui-wrap ui-" + position + "-wrap"}
        }));
      }

      return fragment;
    };

    // container
    container.appendChild(factory.element({
      "type": "div",
      "direct": {
        "id": id,
        "className": (spec.class_list || " ") +
          (spec.fixed ? " ui-header-fixed " : " ") + " ui-header " +
          " slidedown ui-bar-" + (spec.theme || "inherit")
      },
      "attributes": {
        "data-role": "header",
        "data-theme": spec.theme,
        "data-enhanced": "true",
        "role": "banner"
      },
      "logic": {
        "data-reference": spec.reference || null,
        "data-position": spec.fixed ? "fixed" : null
      }
    }));

    return {
      "fragment": container,
      "child_selector": container.querySelector("#" + id) || container,
      "target": target,
      "target_selector": "last",
      // TODO: remove this...
      "spec": {
        "img": !!set_img && !img.src_set,
        "src": img.src,
        "src_set": img.src_set,
        "title": img.alt || spec.title,
        "title_i18n": img.alt_i18n || spec.title_i18n,
        "section_list": spec.section_list,
        "add_content": spec.add_content
      }
    };
  };

  /* ********************************************************************** */
  /*                         JQM Controlgroup                               */
  /* ********************************************************************** */
  /**
   * Generate an controlgroup = a button or action menu
   * Full spec (property_dict) example (with defaults):
   * {
   *   "generate": "widget",
   *   "type": "controlgroup",
   *   "property_dict": {
   *     "id": null,
   *     "class_list": null,
   *     "mini": null
   *     "theme": null,
   *     "direction": "horizontal",
   *     "label": {
   *       "text": null,
   *       "text_i18n": null,
   *       "title": null,
   *       "title_i18n": null
   *     }
   *   },
   *   "children": [
   *     {"type":a, "direct": {}, "attributes": {}, "logic": {}}
   *   ]
   * }
   * @method controlgroup
   * @param {object} spec Configuration for controlgroup
   * @return controlgroup
   */
  factory.widget.controlgroup = function (spec) {
    var container, group, direction, contain, label, i18n, target, generator;

    container = document.createDocumentFragment();
    direction = spec.direction || "vertical";

    // NOTE: will only be used for dynamic element creation
    // TODO: what does this do here?
    generator = function (generator_spec) {
      var element = generator_spec.item;
      // TODO: no!
      if (element.center) {
        element.text = element.center[0].title;
        element.text_i18n = element.center[0].title_i18n;
      }
      element.iconpos = "right";
      return {
        "type": "item",
        "content": factory.util.generateContentElement("link", element, {})[0]
      };
    };

    // controlgroup label
    if (spec.label) {
      // TODO: how to get required on the legend? or off?
      // NOTE: this will always wrap controlgroups!!!
      i18n = spec.label.title_i18n || spec.label.text_i18n;
      contain = factory.element({
        "type": "div",
        "direct": {"className": "ui-fieldcontain translate"},
        "attributes": {"title": spec.label.title},
        "logic": {
          "data-i18n": i18n ? ("[title]" + spec.label.title_i18n) : null
        }
      });
      label = factory.element({
        "type": "div",
        "direct": {"className": "ui-controlgroup-label"}
        //,
        //"attributes": {"role": "heading"}
      });
      label.appendChild(factory.element({
        "type": "legend",
        "direct": {
          "className": "translate required_label"
        },
        "attributes": {
          "data-i18n": spec.label.text_i18n
        }
      }));
      contain.appendChild(label);
    }

    // group
    group = factory.element({
      "type": "div",
      "direct": {
        "className": "ui-corner-all ui-controlgroup " +
          (spec.class_list || "") + " ui-controlgroup-" + direction +
            (spec.mini ? " ui-mini" : "")
      },
      "logic": {
        "id": spec.id || null,
        "data-persist": spec.persist || null
      }
    });

    // controls
    group.appendChild(factory.element({
      "type": "div",
      "direct": {"className": "ui-controlgroup-controls"}
    }));

    target = contain || container;

    // add fieldcontain label
    target.appendChild(group);

    return {
      "fragment": target,
      "child_selector": target.querySelector("div.ui-controlgroup-controls"),
      "child_constructor": generator,
      "child_mapper": spec.map_children
    };
  };

  /* ********************************************************************** */
  /*                            JQM POPUP                                   */
  /* ********************************************************************** */
  /**
   * Generate a pre-enhanced popup
   * Full spec (property_dict) example (with defaults):
   * {
   *   "generate": "widget",
   *   "type":"popup",
   *   "property_dict": {
   *     "id": null,
   *     "theme": null,
   *     "class_list": "",
   *     "overlay-theme": null,
   *     "transition": "fade",
   *     "position-to": "window",
   *     "tolerance": "30,30,30,30",
   *     "shadow": true
   *   },
   *   "children": [],
   * }
   * @method popup
   * @param  {object} spec JSON configuration for popup to be generated
   * @return {object} object including fragment, child-selector, placeholder
   **/
  factory.widget.popup = function (spec) {
    var container, id, wrap;

    container = document.createDocumentFragment();
    id = spec.id || ((spec.data_url || "global") + "-popup");

    // container
    container.appendChild(factory.element({
      "type": "div",
      "direct": {
        "className": "ui-popup-screen ui-screen-hidden " +
            (spec.overlay_theme ?
              ("ui-overlay-" + spec.overlay_theme) : ""),
        "id": id + "-screen"
      }
    }));

    // popup wrapper
    wrap = factory.element({
      "type": "div",
      "direct": {
        "className": "ui-popup-container ui-corner-all ui-popup-hidden " +
          " ui-popup-truncate " + (spec.transition || "fade"),
        "id": id + "-popup"
      }
    });

    // add popup
    wrap.appendChild(factory.element({
      "type": "div",
      "direct": {
        "className": "ui-popup ui-body-" + spec.theme +
          (spec.shadow ? " ui-overlay-shadow " : " ") +
          " ui-corner-all " + spec.class_list,
        "id": id
      },
      "attributes": {
        "data-transition": spec.transition || "fade",
        "data-role": "popup",
        "data-enhanced": "true",
        "data-position-to": spec.position || "window",
        "data-tolerance": spec.tolerance || "30,30,30,30"
      },
      "logic": {
        "data-reference": spec.reference || null,
        "data-theme": spec.theme || null,
        "data-overlay-theme": spec.overlay_theme || null
      }
    }));

    // assemble
    container.appendChild(wrap);

    // TODO: how to add panel to local page_dict
//      // add to DOM if scoped
//     if (url_dict.data_url) {
//       util.getPage(url_dict.data_url).appendChild(container);
//       // and return the placeholder for JQM
//       return placeholder;
//     } else {
//       document.body.appendChild(container);
//     }

    return {
      "fragment": container,
      "child_selector": container.querySelector("div#" + id),
      "placeholder": factory.element({
        "type": "div",
        "direct": {"id": id + "-placeholder"},
        "attributes": {"style": "display:none;"}
      })
    };
  };

  /* ********************************************************************** */
  /*                            JQM Footer                                  */
  /* ********************************************************************** */
  /**
   * Generates JQM footer with navbar or controlgroup based on JSON config
   * Full spec (property_dict) example (with defaults):
   * {
   *   "generate":"widget",
   *   "type": "footer",
   *   "property_dict": {
   *     "fixed": true,
   *     "class_list": null,
   *     "id": "null",
   *     "theme": "slapos-white",
   *   },
   *   "children": []
   * }
   * @method footer
   * @param {object} spec JSON configuration for footer to generate
   * @return {object} HTML fragment
   */
  factory.widget.footer = function (spec) {
    var container, id;

    id = spec.id || ((spec.data_url || "global") + "-footer");
    container = document.createDocumentFragment();

    container.appendChild(factory.element({
      "type": "div",
      "direct": {
        "id": id,
        "className": "ui-footer " + (spec.class_list || " ") +
          (spec.fixed ? "ui-footer-fixed " : " ") +
          "slideup ui-bar-" + (spec.theme || "inherit")
      },
      "attributes": {
        "data-role": "footer",
        "data-theme": spec.theme,
        "data-enhanced": true,
        "role": "contentinfo"
      },
      "logic": {
        "data-reference": spec.reference || null,
        "data-position": spec.fixed ? "fixed" : undefined
      }
    }));

    return {
      "fragment": container,
      "child_selector": container.querySelector("div#" + id)
    };
  };

  /* ********************************************************************** */
  /*                            JQM Navbar                                  */
  /* ********************************************************************** */
  /* Generate a navbar
   * Full spec (property_dict) example (with defaults):
   * {
   *  "generate": "widget",
   *  "type": "navbar",
   *    "property_dict": {
   *    "class_list": null,
   *    "id": null,
   *    "theme": "slapos-white"
   *  },
   *  "children":[]
   * }
   * @method navbar
   * @param {object} spec Configuration options to create a nabar
   * @returns navbar HTML fragment
   */
  factory.widget.navbar = function (spec) {
    var container, controls, element, target;

    container = document.createDocumentFragment();
    target = function (spec) {
      return factory.element({
        "type": "li",
        "direct": {
          "className": "ui-block-" + util.toLetter(spec.j + 1).toLowerCase()
        }
      });
    };

    // controls
    controls = factory.element({
      "type": "ul",
      "direct": {
        "className": "ui-grid-" +
          util.toLetter(spec.length - 1).toLowerCase()
      }
    });

    // navbar
    element = factory.element({
      "type": "div",
      "direct": {"className": "navbar ui-navbar " + (spec.class_list || "")},
      "attributes": {
        "role": "navigation"
      },
      "logic": {"data-reference": spec.reference || null}
    });
    element.appendChild(controls);
    container.appendChild(element);

    return {
      "fragment": container,
      "child_selector": container.querySelector("div.ui-navbar").children[0],
      "target": target
    };
  };

  /* ********************************************************************** */
  /*                              JQM Controlbar                            */
  /* ********************************************************************** */
  /**
   * Generate a generic toolbar with controls (also used for table wrappers
   * and form field containers)
   * Full spec (property_dict) example (with defaults):
   * {
   *   "generate": "widget",
   *   "type": "controlbar",
   *   "property_dict" : {
   *     "slot": null
   *     "class_list": ""
   *   },
   *   "children": []
   * }
   * @method controlbar
   * @param {object} spec JSON configuration
   * @return {object} fragment
   */
  factory.widget.controlbar = function (spec) {
    var container, slot, pass, class_list;

    pass = function (container) {
      return {
        "fragment": container,
        "child_selector": container.querySelector("div") || container,
        "add_on_dom": spec.target === "document"
      };
    };

    container = document.createDocumentFragment();

    switch (spec.wrap) {
    case "fragment":
      return pass(container);
    case "section":
      class_list = spec.class_list;
      break;
    default:
      slot = spec.slot;
      class_list = (slot ? "" : "ui-controlbar ") + (spec.class_list || "");
      break;
    }

    container.appendChild(factory.element({
      "type": "div",
      "direct": {
        "className": class_list
      },
      "logic": {
        "data-persist": spec.persist || null,
        "data-wrap": slot ? null : true,
        "data-slot": slot ? true : null,
        "data-slot-id": slot || null,
        "data-reference": spec.reference || null
      }
    }));

    return pass(container);
  };

  /* ********************************************************************** */
  /*                             JQM PANEL                                  */
  /* ********************************************************************** */
  /**
   * Generates a JQM panel (needs enhancement!)
   * Full spec (property_dict) example (with defaults):
   * {
   *   "generate": "widget",
   *   "type": "panel",
   *   "property_dict" : {
   *     "close": true
   *     "class_list": null,
   *     "id": null,
   *     "theme": null,
   *   },
   *   "children": []
   * }
   * @method panel
   * @param  {object} spec JSON configuration
   * @return {object} HTML fragment
   */
  // NOTE: Needs JQM pre-enhancement if outside page!
  factory.widget.panel = function (spec) {
    var container, id, wrap, closer;

    container = document.createDocumentFragment();
    id = spec.id || ((spec.data_url || "global") + "-panel");

    // content wrapper with optional close button
    wrap = function (spec) {
      var fragment, element, count, len;

      count = spec.j;
      len = spec.len;
      if (count === 0) {
        fragment = document.createDocumentFragment();
        element = factory.element({
          "type": "div",
          "direct": {
            "className": "panel_element panel_element_first panel_header "
          }
        });
        if (closer) {
          element.appendChild(closer);
        }
        fragment.appendChild(element);
      } else if (count === len - 1) {
        fragment = factory.element({
          "type": "div",
          "direct": {"className": "panel_element panel_element_last"}
        });
      } else {
        fragment = factory.element({
          "type": "div",
          "direct": {"className": "panel_element"}
        });
      }
      return fragment;
    };

    // panel
    container.appendChild(factory.element({
      "type": "div",
      "direct": {"id": id, "className": "panel " + (spec.class_list || "")},
      "attributes": {
        "data-role": "panel",
        "data-theme": spec.theme,
        "data-position": spec.position || "left",
        "data-display": "push",
        "data-position-fixed": true
      },
      "logic": {"data-reference": spec.reference || null}
    }));

    // panel close button
    if (spec.close) {
      closer = factory.element({
        "type": "a",
        "direct": {
          "href": "#",
          "className": "ui-panel-close ui-icon-remove ui-btn " +
            "ui-btn-icon-notext ui-shadow ui-corner-all"
        },
        "attributes": {
          "data-rel": "close"
        },
        "logic": {"text": "Close"}
      });
    }

    return {
      "fragment": container,
      "child_selector": container.querySelector("div#" + id),
      "target": wrap,
      "target_selector": "first",
      "closer": closer,
      "add_on_page": spec.local
    };
  };

  /* ********************************************************************** */
  /*                                 JQM Form                               */
  /* ********************************************************************** */
  /**
   * Generate a JQM form
   * @method page
   * Full spec (property_dict) example (with defaults):
   * {
   *   "generate": "widget",
   *   "type": "form",
   *   "property_dict" : {
   *      "secure": "default|captcha",
   *      "secret_hash": "foo",
   *      "public_key": "6Ldpb-oSAAAAAGwriKpk4ol1n4yjN_as6M4xv0zA",
   *      "method": "POST",
   *      "action": "#"
   *   },
   *   "children": []
   * }
   * Generate a form
   * @method form
   * @param {object} spec Configuration for controlgroup
   * @return HTML fragment
   */
  factory.widget.form = function (spec) {
    var container, form_id, form, item_id, stamp, captcha, sauce, wrap;

    container = document.createDocumentFragment();
    form_id = "form_" + util.uuid();
    item_id = spec.data ? (spec.data._id || undefined) : undefined;
    if (spec.update) {
      form = document.createDocumentFragment();
    } else {
      form = factory.element({
        "type": "form",
        "direct": {
          "id": form_id,
          "method": "POST",
          "action": "#",
          "className": (spec.class_list || "")
        },
        "attributes": {
          "data-ajax": false,
          "autocomplete": "off",
          "data-widget": "form"
        },
        "logic": {
          "data-reference": spec.reference || null,
          "data-map": spec.map_children || null
        }
      });
    }

    // NOTE: needed to differentiate between PUT/POST
    // TODO: block_identifier is only used for release_install, find better!
    if (item_id && !spec.block_identifier) {
      form.appendChild(factory.element({
        "type": "input",
        "attributes": {
          "type": "hidden",
          "value": item_id,
          "name": "identifier"
        }
      }));
    }

    // TODO: "secure" form
    switch (spec.secure) {
    case "default":
      stamp = new Date().getTime();
      // TODO: only if shim is available!
      //encode = window.btoa || window.shim.Base64.encode;
      //sauce = encode(stamp.toString() + spec.secret_hash);
      sauce = undefined;

      form.appendChild(factory.element({
        "type": "input",
        "direct": {"type": "text", "value": "", "className": "secure_form"},
        "attributes": {
          "placeholder": "Please do not fill out this field",
          "data-enhanced": "true",
          "data-i18n": null
        },
        "logic": {
          "label": "Please do not fill out this field",
          "label_i18n": null
        }
      }));
      form.appendChild(factory.element({
        "type": "input",
        "direct": {
          "type": "hidden",
          "value": spec.secret_hash,
          "id": form_id + "_not_a_secret"
        },
        "attributes": {"data-created": stamp}
      }));
      form.appendChild(factory.element({
        "type": "input",
        "direct": {
          "type": "text",
          "className": "secure_form",
          "value": sauce
        },
        "attributes": {
          "placeholder": "Please leave this value unchanged",
          "data-enhanced": "true",
          "data-i18n": "[placeholder]global.form_helpers.secure_filled"
        },
        "logic": {
          "label": "Please leave this value unchanged",
          "label_i18n": "global.form_helpers.secure_filled"
        }
      }));
      break;
    case "captcha":
      if (spec.public_key === undefined) {
        util.error("Captcha - No public key supplied");
      }
      // goes to the end of the form, so set as placeholder
      captcha = factory.element({
        "type": "div",
        "direct": {"id": "captcha_" + form_id},
        "attributes": {"data-key": spec.public_key}
      });
      break;
    }

    // assemble
    container.appendChild(form);
    // TODO: WHY?
    wrap = undefined;
    return {
      "fragment": container,
      "child_selector": container.querySelectorAll("form")[0],
      "child_mapper": spec.map_children,
      "placeholder": captcha,
      "target": wrap,
      "target_selector": "first",
      "reverse": spec.reverse || null,
      "reference": spec.reference
    };
  };

  /* ********************************************************************** */
  /*                                JQM Carousel                            */
  /* ********************************************************************** */
  /**
   * Generate a JQM carousel
   * Full spec (property_dict) example (with defaults):
   * {
   *   "generate": "widget",
   *   "type": "carousel",
   *   "property_dict" : {
   *    "id": null,
   *    "theme": null,
   *    "class_list": null,
   *    "handles": null,
   *    "bullets": true,
   *    "bulletspos": "bottom"
   *    "inset": null,
   *    "shadow": null,
   *    "corners": null,
   *    "transition": slide,
   *    "captions": null,
   *    "captionpos": null,
   *    "captiontheme": null,
   *    "thumbnail": null,
   *    "controller_id": null
   *    },
   *   "children": [
   *      {
   *        "href": null,
   *        "caption": {"text": null, "text_i18n": null},
   *        "content": [
   *          {"type": "img", "direct": {"src": "foo.html", "alt":""}...}
   *        ]
   *      }
   *    ]
   * }
   * @method carousel
   * @param {object} spec JSON configuration
   * @return {object} object including fragment, child-selector, placeholder
   **/
  factory.widget.carousel = function (spec) {
    var fragment, generator, empty_string, has_handles, make_handle, is_thumb,
      controller, i, id, radio_id, len;

    // makes carousel elements
    generator = function (generator_spec) {
      var element, target, has_link, count, item;

      element = generator_spec.item;
      count = generator_spec.count;
      has_link = element.href;
      item = factory.element({
        "type": "li",
        "direct": {"className": (count === 0 ? "ui-carousel-active" : "")}
      });

      if (has_link) {
        target = factory.element({
          "type": "a",
          "direct": {
            "href": has_link,
            "className": "ui-link ui-carousel-selector"
          }
        });
      } else {
        target = item;
      }

      // generate contents
      target.appendChild(factory.util.generateContent(element)[0]);

      if (has_link) {
        item.appendChild(target);
      }

      return {
        "type": "item",
        "content": item
      };
    };

    // make handle
    make_handle = function (direction) {
      return factory.element({
        "type": "a",
        "direct": {
          "href": "#",
          "className": "ui-carousel-handle ui-carousel-handle-" + direction +
            " ui-btn ui-btn-icon-notext ui-corner-all ui-icon-carat-" +
              direction.slice(0, 1) + " ui-shadow translate"
        },
        "attributes": {"data-i18n": "global_dict." + direction},
        "logic": {"text": direction}
      });
    };

    fragment = document.createDocumentFragment();
    empty_string = "";
    has_handles = spec.handles;
    is_thumb = spec.thumbnails;
    id = spec.controller_id || spec.id || util.uuid();

    // list container - we spare declaring data-attributes
    fragment.appendChild(factory.element({
      "type": "ul",
      "direct": {
        "role": "carousel",
        "className": (spec.class_list || empty_string) + " ui-carousel " +
          (is_thumb ? " ui-carousel-thumbnails " : " ui-carousel-bullets " +
            ((has_handles ? " ui-carousel-handles" : empty_string) +
              (spec.shadow ? " ui-shadow" : empty_string) +
                (spec.captions ? " ui-carousel-captions" : empty_string) +
                  (spec.inset ? " ui-carousel-inset" : empty_string) +
                    (spec.corners ? " ui-corner-all" : empty_string))) +
                      (" ui-body-" + (spec.theme || "inherit"))
      },
      "attributes": {
        "data-enhanced": true,
        "data-barrel": "ui-carousel-barrel-" + id,
        "data-role": "carousel",
        "tabindex": -1
      },
      "logic": {
        "id": spec.id || null
      }
    }));

    // handles
    if (has_handles) {
      fragment.appendChild(make_handle("right"));
      fragment.appendChild(make_handle("left"));
    }

    // bullets
    if (!is_thumb) {
      controller = factory.element({
        "type": "div",
        "direct": {
          "id": "ui-carousel-barrel-" + id,
          "className": "ui-carousel-controls " +
            "ui-carousel-controls-" + (spec.bulletspos || "bottom")
        },
        "attributes": {},
        "logic": {}
      });

      len = spec.total_rows || spec.length;

      for (i = 0; i < len; i += 1) {
        radio_id = "radio-" + id;
        controller.appendChild(factory.widget.formElement({
          "type": "input",
          "label": true,
          "direct": {
            "id": (radio_id + "-" + i),
            "name": radio_id,
            "checked": (i === 0 ? "checked" : "")
          },
          "attributes": {
            "type": "radio",
            "data-icon": "circle",
            "data-iconpos": "notext"
          },
          "logic": {"value": i}
        }));
      }
      fragment.appendChild(controller);
    }

    return {
      "fragment": fragment,
      "child_selector": fragment.querySelector("ul"),
      "child_constructor": generator,
      "child_mapper": spec.map_children,
      "base": "li"
    };
  };

  /* ********************************************************************** */
  /*                                JQM Listview                            */
  /* ********************************************************************** */
  /*
   * Generate a JQM listview
   * Default spec with defaults and single list item with all options!
   * {
   *   "generate": "gadget",
   *   "type": "listview",
   *   "property_dict": {
   *     "class_list": "",
   *     "theme": "slapos-black",
   *     "alt_icon": null
   *     "numbered": false
   *     "inset": true,
   *     "reveal": true,
   *     "filter": true,
   *     "input": "#foo"
   *     "placeholder": null,
   *     "filter_theme": null,
   *     "divider_theme": "slapos-white",
   *     "autodividers": true,
   *     "count_theme": "slapos-white",
   *   },
   *   "children": [
   *   {
   *     "type": "item/divider",
   *     "id": null,
   *     "external": true,
   *     "href": "index.html",
   *     "icon": "foo"/null,
   *     "title": null,
   *     "title_i18n":"",
   *     "left": [
   *       {"type": "icon", "icon":"foo"},
   *       {
   *        "type": "image":
   *        "src": "http://www.xyz.com/img/foo.png",
   *        "alt": null,
   *        "alt_i18n": null
   *       }
   *     ],
   *     "center": [
   *      {"type":"text", "aside": true, "text":"foo", "text_i18n":null},
   *      {"type": "count", "count": true, "text": "3689"}
   *     ],
   *     "right": [
   *      {"type": "radio/checkbox", "action": "foo"},
   *      {
   *        "type": "link",
   *        "action": "foo",
   *        "icon": "bar",
   *        "external": true,
   *        "title": "ha",
   *        "title_i18n": "ha",
   *        "href": "http://foo.com"
   *      }
   *    ]
   *  }
   * @method listview
   * @param {object} spec Configuration for controlgroup
   * @return configuration object including wrapper, child-selector etc
   */
  // TODO: add collapsible support if needed
  // TODO: dividers? will not be in spec, so can only be listview option!
  // TODO: find better way to set mapper/widget
  factory.widget.listview = function (spec) {
    var fragment, generator;

    // generate list item
    generator = function (generator_spec) {
      var divider, static_item, theme, config, auto, last, item, target, list,
        content, element, count;

      element = generator_spec.item;
      config = generator_spec.wrapper.property_dict;
      count = generator_spec.i;
      divider = element.type === "divider" ? true : undefined;
      static_item = (element.href === undefined && !divider) ? true : undefined;
      theme = config.divider_theme || config.theme || "inherit";

      // autodividers (TODO: does this work? NO!!! do with i!)
      // NOTE: it should also work without enhancing the widget, because
      // the list items are re-generated on refresh, so autodividers can be
      // aswell
      if (config.autodividers) {
        auto = element.text[0].text.slice(0, 1).toUpperCase();
        if (last !== auto) {
          list = document.createDocumentFragment();
          list.appendChild(factory.element({
            "type": "li",
            "direct": {
              "className": "ui-li-divider ui-bar-" + theme +
                  (count === 0 ? " ui-first-child" :
                    ((count === config.length - 1) ?
                        " ui-last-child" : "")) + (element.class_list || "")
            },
            "attributes": {},
            "logic": {"text": auto}
          }));
          last = auto;
        }
      }

      // static item, divider or link
      if (static_item || divider) {
        target = document.createDocumentFragment();
      } else {
        element.is_main = true;
        target = factory.util.generateContentElement(
          "link",
          element,
          config
        )[0];
      }

      // generate contents, split button will return [frag1, frag2, class_list]
      content = factory.util.generateContent(element, "ui-li-has-alt");
      target.appendChild(content[0]);

      // list item spec
      item = factory.element({
        "type": "li",
        "direct": {
          "className": divider ? ("ui-li-divider ui-bar-" + theme) : " " +
            (count === 0 ? " ui-first-child " :
                ((count === config.length - 1) ?
                    " ui-last-child " : " ")) + content.pop() +
            (static_item ? " ui-li-static ui-body-inherit " : " ") +
            (config.form_item ? "ui-field-contain " : " ") +
            (config.reveal ? "ui-screen-hidden " : " ") +
            (element.class_list || "")
        }
      });

      // assemble
      item.appendChild(target);

      // link-alt
      if (!!content[1]) {
        item.appendChild(content[1]);
      }

      return {
        "type": "item",
        "content": item
      };
    };

    // listview starts here
    fragment = document.createDocumentFragment();

    // search filter
    if (spec.update !== true) {
      // NOTE: if input provided, the filter is already there!
      // THIS MAKES B
      if (spec.filter && !spec.input) {
        fragment.appendChild(factory.widget.formElement(
          factory.util.searchBarTemplate({
            "search_id": spec.id || "search",
            "text": spec.text || "",
            "text_i18n": spec.text_i18n || null
          })
        ));
      }

      fragment.appendChild(factory.element({
        "type": spec.numbered ? "ol" : "ul",
        "direct": {
          "className": "ui-listview " + (spec.class_list || "") +
            (spec.inset ?
                " ui-listview-inset ui-corner-all ui-shadow " : "")
        },
        "attributes": {
          // NOTE: on updates, we need wrapper with generator and mapper???
          "data-widget": "listview"
        },
        "logic": {
          "id": spec.id || null,
          "data-update": spec.dynamic ? true : null,
          "data-reference": spec.reference || null,
          "data-map": spec.map_children || null
        }
      }));
    }

    return {
      "fragment": fragment,
      "child_selector": spec.update !== true ?
          fragment.querySelector("ul") : fragment,
      "child_constructor": generator,
      "child_mapper": spec.map_children,
      "base": "li",
      "foo": "listview"
    };
  };

  /* ********************************************************************** */
  /*                                JQM Table                               */
  /* ********************************************************************** */
  /* Generate a JQM table
   * Default scheme:
   * {
   *   "generate": "gadget",
   *   "type": "table",
   *   "class_list": "",
   *   "theme": "slapos-black",
   *   "property_dict": {
   *     "inset": true,
   *     "filter": true,
   *     "input": "#foo"
   *     "mode": "columntoggle",
   *     "toggle_popup": false,
   *     "wrap": "both",
   *     "top_grid": 3,
   *     "bottom_grid": 1
   *     "sorting": true
   *   },
   *   "children": [
   *      "section": {
   *        "type": "tableHeader|Body|Footer",
   *        "properties": {
   *          "config": { object containing features, id, portal_type },
   *          "rows": [
   *            [
   *              {
   *                "column": "column_name",
   *                "id": "id",
   *                "href": "linkable rows",
   *                "text": "foo",
   *                "external": true/null
   *                "text_i18n": "dict.foo",
   *                "show": true,
   *                "priority": 1,
   *                "sort": true,
   *                "merge": "column_baz",
   *                "merger_text": "",
   *                "merger_i18n": "dict.baz",
   *                "image": {
   *                  "src": "foo.jpg",
   *                  "alt": "foo",
   *                  "alt_i18n": "dict.foo"
   *                },
   *                "action_list": [{
   *                  "href": "",
   *                  "text": "foo",
   *                  "text_i18n": "bar",
   *                  "action": "baz",
   *                  "class_list": "for",
   *                  "icon": "bam"
   *                  }, { next action }
   *                ],
   *              }, { next cell },...
   *          ], [ next row ], ...
   *        ]
   *      }
   *    }, { next section }
   *  ]
   * @method table
   * @param {object} spec Configuration for table
   * @return configuration object including wrapper, child-selector etc
   */
  // TODO: pre-enhance!
  factory.widget.table = function (spec) {
    var fragment, container, generator, counter, section, i, tag, image;

    counter = function (scheme) {
      var m, field, k;

      for (k = 0, m = 0; m < scheme.field_list.length; m += 1) {
        field = scheme.field_list[m];
        if ((field.field || field.custom) && field.merge === undefined) {
          k += 1;
        }
      }
      return k;
    };

    // TODO: I must be kidding me...
    image = function (config) {
      if (typeof config.image === "object") {
        return config.image.src;
      }
      return config.text;
    };

    generator = function (generator_spec) {
      var quirk_dict, row, temp, cell, j, field, link, logic, faux_id,
        attributes, property, snippet, title, k, button, group, dict, set,
        wrap, lookup, item_href, element;

      element = generator_spec.item;
      quirk_dict = generator_spec.wrapper.property_dict;
      wrap = generator_spec.wrapper.wrap;
      temp = {};
      row = factory.element({
        "type": "tr",
        "direct": {"className": quirk_dict.link ? "linkable" : ""}
      });

      // checkbox/radio
      if (!quirk_dict.no_items && (quirk_dict.check || quirk_dict.radio)) {
        if (quirk_dict.global_check) {
          element.action = "check_all";
        }
        if (quirk_dict.check && !quirk_dict.global_check) {
          element.action = "check_all_visible";
        }
        cell = factory.element({"type": "th"});
        faux_id = wrap ? element.action : "select_" + element[0].id;
        dict = {
          "type": quirk_dict.check ? "checkbox" : "radio",
          "data-i18n": "[value]",
          "data-enhanced": true,
          "value": "Select item",
          "data-iconpos": "notext"
        };
        if (element.action) {
          dict["data-action"] = faux_id;
          dict["data-reference"] = quirk_dict.reference;
        }
        cell.appendChild(factory.widget.formElement({
          "type": "input",
          "direct": {
            "id": (wrap ? quirk_dict.reference + "_" : "") + faux_id,
            "name": quirk_dict.check ? faux_id : "radio_" +
              quirk_dict.reference,
            "className": element.action ? "action" : ""
          },
          "attributes": dict,
          "wrap": false,
          "label": true
        }));
        row.appendChild(cell);
      }

      // NOTE: when merging columns we start from the end so we can store
      // values of columns to merge
      if (quirk_dict.mergeable) {
        element = util.reverseArray(element);
      }

      // loop cells
      for (j = 0; j < element.length; j += 1) {
        field = element[j];
        link = undefined;
        attributes = {};
        logic = {
          "colspan": field.colspan || null
        };
        property = field.field;

        if (field.show) {
          if (field.merge === undefined) {
            if (field.text_i18n) {
              attributes["data-i18n"] = field.text_i18n;
            }
            if (!field.persist) {
              attributes["data-priority"] = field.priority || 6;
            }
            // header/footer
            if (wrap && !field.custom) {
              lookup = quirk_dict.field_dict[property].widget;
              field.text = lookup.title;
              field.text_i18n = lookup.title_i18n || null;
            }

            // set title (title or merge_title stored in temp)
            snippet = (field.text || field.title);
            title = temp[property] ? snippet + " " + temp[property][0] :
                snippet;

            // action menu (calling widget is cheating...)
            if (field.action_list) {
              group = factory.widget.controlgroup({"direction": "horizontal"});
              for (k = 0; k < field.action_list.length; k += 1) {
                button = field.action_list[k];
                if (field.id) {
                  item_href = window.encodeURIComponent(field.id);
                }
                group.child_selector.appendChild(factory.element({
                  "type": "a",
                  "direct": {
                    "href": item_href || button.href || "#",
                    "className": (button.class_list || "") +
                        (button.text_i18n ? " translate " : " ") + "ui-btn " +
                            "ui-btn-icon-notext ui-shadow ui-corner-all" +
                                " ui-icon-" + button.icon + " action"
                  },
                  "attributes": {
                    "data-action": button.action,
                    "data-reference": quirk_dict.reference
                  },
                  "logic": {
                    "data-i18n": button.text_i18n || null,
                    "text": button.text
                  }
                }));
              }
              // hijack "link" (fragment should include buttons)
              link = group.fragment;

            // link/sorting
            } else if (quirk_dict.link && (field.href ||
                (wrap && quirk_dict.sortable && field.sort))
                ) {
              // header link (sorting) or href link
              link = factory.element({
                "type": "a",
                "direct": {
                  "className": field.href ? "table_link ui_link" :
                      "action ui-sorting ui-btn ui-icon-sort " +
                          "ui-icon ui-btn-icon-right" +
                              (field.text_i18n ? " translate" : "")
                },
                "attributes": {},
                "logic": {
                  "data-action": field.href ? null : "sort",
                  "data-external": field.external || null,
                  "data-reference": field.href ? null : quirk_dict.reference,
                  "data-column-title": field.href ? null : field.field,
                  "text": field.image ? null : title,
                  "img": field.image ? image(field) : null,
                  "href": field.href || null,
                  "data-i18n": field.href ? null : (field.text_i18n ?
                      (temp[property] ? temp[property][1] : field.text_i18n) :
                          null)
                }
              });
            // image
            } else if (field.image) {
              logic.img = image(field);
            } else {
              logic.text = title;
            }

            cell = factory.element({
              "type": wrap ? "th" : "td",
              "direct": {
                "className": (field.class_list || "") +
                    (link ? " " : " translate plain")
              },
              "attributes": attributes,
              "logic": logic
            });

            // add the link/action menu
            if (link) {
              cell.appendChild(link);
            }

            // insert
            if (quirk_dict.mergeable) {
              row.insertBefore(
                cell,
                (set === undefined ? null :
                    row.childNodes[quirk_dict.check ? 1 : 0])
              );
              set = true;
            } else {
              row.appendChild(cell);
            }
          } else {
            // store value to merge until reaching specified cell
            temp[field.merge] = [
              (field.merger_text || field.text),
              (field.merger_i18n || null)
            ];
          }
        }
      }

      return {
        "type": "item",
        "content": row
      };
    };

    // table starts here
    fragment = document.createDocumentFragment();

    if (spec.update !== true) {
      container = factory.element({
        "type": "table",
        "direct": {
          "className": (spec.class_list || "") + " ui-responsive table-stroke"
        },
        "attributes": {
          "data-role": "table",
          "data-mode": spec.mode,
          "data-use-pop": spec.toggle_popup === false ? false : true
        },
        "logic": {
          "id": spec.id || null,
          "data-filter": spec.filter || null,
          "data-input": spec.input || null,
          "data-wrap": spec.wrap || null,
          "data-top-grid": spec.top_grid || null,
          "data-bottom-grid": spec.bottom_grid || null,
          "data-sorting": spec.sorting || null
        }
      });

      // header
      for (i = 0; i < spec.scheme.length; i += 1) {
        section = spec.scheme[i];
        switch (section.position) {
        case "header":
          tag = factory.element({"type": "thead"});
          tag.appendChild(
            generator({
              "item": section.field_list,
              "wrapper": {
                "property_dict": spec,
                "wrap": true
              }
            }).content
          );
          container.appendChild(tag);
          break;
        case "body":
          container.appendChild(factory.element({
            "type": "tbody",
            "attributes": {
              "data-update": "true",
              "data-widget": "table"
            },
            "logic": {"data-map": spec.map_children || null}
          }));
          break;
        }
      }
      fragment.appendChild(container);
    }
    return {
      "fragment": fragment,
      "child_selector": fragment.querySelector("tbody"),
      "child_constructor": generator,
      "child_mapper": spec.map_children,
      "base": "tr",
      "count": counter
    };
  };


  /* ********************************************************************** */
  /*                            CRUMBS                                      */
  /* ********************************************************************** */
  /**
   * Make a list | of | links
   * @method breadcrumb
   * @param {object} spec Configuration object
   * @return {object} HTML fragment
   */
  factory.crumbs = function (spec) {
    var i, crumb, base, breadcrumb, makeLink, len, pass, icon, patch, space;

    len = (spec.children || []).length;
    base = "";
    space = document.createTextNode("\u00A0");
    patch = "#";
    makeLink = function (config) {
      icon = config.home || config.icon;
      return factory.element({
        "type": "a",
        "direct": {
          "href": config.href || "#",
          "className": (icon ? "ui-btn ui-btn-icon-notext ui-icon-" +
              icon + " ui-shadow ui-corner-all" : "ui-link") +
                 " translate"
        },
        "attributes": {
          "data-i18n": "global_dict." + config.href.replace(/[#\/]/g, '')
        },
        "logic": {
          "data-action": config.action || null,
          "data-info": config.info || null,
          "text": config.home ? "Home" : (config.text || "\u00A0")
        }
      });
    };

    // base element
    breadcrumb = factory.element({
      "type": "span",
      "direct": {"className": "crumbs"},
      "logic": {
        "data-info": spec.data_url ? "url" : null,
        "data-reference": spec.data_url || null
      }
    });

    // home
    if (spec.home) {
      breadcrumb.appendChild(
        makeLink({"href": app.property_dict.home, "home": "home"})
      );
    }

    // fragments
    for (i = 0; i < len; i += 1) {
      crumb = spec.children[i];
      if (typeof crumb === "string") {
        pass = {
          "href": base + (patch + crumb),
          "text": crumb
        };
        patch = "/";
      }
      breadcrumb.appendChild(space);
      breadcrumb.appendChild(makeLink(pass || crumb));
      breadcrumb.appendChild(space);
    }

    return breadcrumb;
  };

  /**
   * Loop over object and generate content elements inside array
   * @method generateContent
   * @param {object} config Custom content config object
   * @param {string} split Indicator whether to split content
   * @return {array} [content fragment, split content, class_string]
   **/
  // TODO: evolve into something not requiring this and the next method...
  factory.util.generateContent = function (config, split) {
    var section, arr, len, k, block, content, class_string, fragment, appendix;

    class_string = "";
    fragment = document.createDocumentFragment();

    for (section in config) {
      if (config.hasOwnProperty(section)) {
        arr = config[section];

        if (typeof arr === "object" && arr !== null) {
          for (k = 0, len = arr.length; k < len; k += 1) {
            block = arr[k];
            content = factory.util.generateContentElement(
              block.type,
              block,
              config
            );
            class_string += content[1];

            if (content[1] === split) {
              appendix = content[0];
            } else {
              fragment.appendChild(content[0]);
            }
          }
        }
      }
    }
    // TODO: return an object...
    return [fragment, appendix, class_string];
  };

  /**
   * central method to generate custom-type elements
   * @method generateContentElement
   * @param {string} type Type of custom element
   * @param {object} spec Configuration object
   * @param {object} config Parent element configuration properties
   * @return {array} [element, parent_class]
   **/
  // TODO: class_string only works for listview, could be expanded
  // TODO: this is crap though....
  factory.util.generateContentElement = function (type, spec, config) {
    var id, is_type, is_main, no_icon, input_dict, container;

    switch (type) {

    // ribbon?
    // TODO: what makes a ribbon? How does it look on diff screen sizes?
    case "ribbon":
      container = factory.element({
        "type": "span",
        "direct": {"className": "ribbon-wrapper"}
      });
      container.appendChild(factory.element({
        "type": "span",
        "direct": {"className": "ribbon"}
      }));
      return [container, ""];

    case "caption":
      container = factory.element({
        "type": "p",
        "direct": {
          "className": "ui-carousel-captions-content" +
            " ui-bar-" + (config.theme || config.captiontheme || "inherit") +
              " ui-carousel-captions-" + (config.captionpos || "bottom")
        }
      });
      container.appendChild(factory.element({
        "type": "h1",
        "direct": {"className": "ui-carousel-captions-heading"},
        "logic": {
          "text": spec.text || spec.value || ""
        }
      }));
      return [container, ""];

    // image
    case "image":
      return [factory.element({
        "type": "img",
        "direct": {"src": (spec.src || spec.value), "alt": (spec.alt || "")}
      }), " ui-li-has-thumb "];

    // custom icon > left
    case "icon":
      return [factory.element({
        "type": "span",
        "direct": {
          "className": "ui-li-icon ui-li-icon-custom ui-icon-" +
            (spec.icon || spec.value) + " ui-icon"
        },
        "logic": {"text": "\u00A0"}
      }), " ui-li-has-icon"];

    // main link, split button
    // TODO: make icon generic! what about util.class_string
    case "link":
      is_main = !!spec.is_main;
      no_icon = config.icon === null;
      return [factory.element({
        "type": "a",
        "direct": {
          "href": spec.href || "#",
          "className": "ui-btn " + " ui-btn-" +
            (config.split_theme || config.theme || "inherit") +
              (no_icon ? " " : (is_main ? " ui-btn-icon-right " :
                " ui-btn-icon-" + (spec.iconpos || "notext")) +
                  " ui-icon-" + (config.icon || spec.icon || "carat-r"))
        },
        "attributes": {
          "title": spec.title || "",
          "data-i18n": "[title]" + (spec.title_i18n || "") + ";" +
            (spec.text_i18n || "")
        },
        "logic": {
          "rel": spec.external ? "external" : null,
          "data-ajax": spec.external ? "false" : null,
          "text": spec.text || null
        }
      }), "ui-li-has-alt"];

    // checkbox/radio
    // TODO: reference??
    case "check":
    case "radio":
      // need to do this out here to account for action
      id = util.uuid();
      is_type = type === "check";

      input_dict = {
        "type": is_type ? "checkbox" : "radio",
        "data-i18n": "[value]",
        "data-enhanced": true,
        "value": "Select item",
        "data-iconpos": "notext"
      };
      if (spec.action) {
        input_dict["data-action"] = spec.action;
        input_dict["data-reference"] = config.reference;
      }
      return [factory.widget.formElement({
        "type": "input",
        "direct": {
          "id": "select_" + (spec.id || id),
          "name": "select_" +
              (is_type ? (spec.id || id) :
                  (spec.id || id || config.id).slice(0, -2)),
          "className": spec.action ? "action" : ""
        },
        "attributes": input_dict,
        "logic": {"wrap": false, "add_label": true}
      }), "ui-li-has-alt"];

    // case count bubble
    case "count":
      return [factory.element({
        "type": "span",
        "direct": {
          "className": "ui-li-count ui-body-" +
              (config.count_theme || config.theme)
        },
        "logic": {"text": spec.count}
      }), "ui-li-has-count"];

    // normal elements - either full config or mapped
    default:
      if (spec.direct) {
        return [factory.element(spec), ""];
      }
      return [factory.element({
        "type": spec.type || "span",
        "direct": {
          "className": (spec.aside ? "ui-li-aside " : " ") +
            (spec.text_i18n ? "translate " : " ") +
              (spec.class_list || "")
        },
        "logic": {
          "text": (spec.value || spec.title || spec.text),
          "data-i18n": spec.text_i18n || spec.title_i18n || null
        }
      }), ""];
    }
  };

  /**
   * return a placeholder config in case no items were found
   * @method noItems
   * @param {object} content_dict Type of placeholder to create
   * @param {string} type of element to generate
   * @param {integer} count number of columns (if applicable)
   * @return {object} HTML fragment
   */
  factory.util.noItems = function (content_dict, tag, count) {
    var container, counter, item, actions;

    actions = content_dict.action_list;
    container = [];

    switch (tag) {
    case "tr":
      item = {
        "custom": true,
        "show": true,
        "persist": true,
        "class_list": "ui-no-result"
      };
      if (actions) {
        counter = count - 1;
        container.push(util.mergeObject(
          util.cloneObject(item),
          {"action_list": actions}
        ));
      }

      container.unshift(util.mergeObject(
        util.cloneObject(item),
        {
          "text": content_dict.dynamic || content_dict.message ||
            "No items found.",
          "text_i18n": content_dict.message_i18n ||
            "validation_dict.no_items_found",
          "colspan": counter || count
        }
      ));
      return container;
    case "li":
      return {
        "type": "item",
        "left": {
          "icon": "question"
        },
        "center": {
          "text": [{
            "type": "h1",
            "text": content_dict.dynamic || content_dict.message ||
              "No items found.",
            "text_i18n": content_dict.message_i18n ||
              "validation_dict.no_items_found"
          }]
        }
      };
    default:
      alert("placeholder for no items missing");
      break;
    }
  };

  /* ********************************************************************** */
  /*                            Factory Util                                */
  /* ********************************************************************** */

  /**
   * Make an info field
   * @method infoField
   * @param {string} text to display
   * @param {string} text_i18n translation lookup value
   * @return {object} HTML fragment
   **/
  factory.util.infoField = function (text, text_i18n) {
    return factory.element({
      "type": "span",
      "direct": {"className": "translate"},
      "attributes": {"data-i18n": "global_dict." + text_i18n},
      "logic": {"text": text}
    });
  };

  /**
   * Generates a JQM button string
   * util.classes
   * @param {object} element Configuration
   * @param {string} default_icon Icon to pass as default
   * @param {string} pass Allow to pass through manually (input-wrapper eg)
   * @return {string} finished class string
   **/
  factory.util.classes = function (element, default_icon, pass) {
    var def, string, iconpos, icon;

    string = "";

    // NOTE: we only set on <a> NOT on INPUTs, because they are wrappped!
    if (element.type === "a" || pass) {
      icon = (element.attributes || {})["data-icon"] || default_icon;

      // because of custom a in service_instance_status
      // NOTE: input buttons may need icons, but not ui-btn ui-shadow
      // TODO: this has nothing to do here!!!
      if (!(element.logic || {}).plain_link) {
        string += " ui-btn ui-shadow ";
      }
      if (icon) {
        iconpos = element.attributes["data-iconpos"];
        string += " ui-icon-" + icon.replace("data-", "");

        if (iconpos) {
          def = iconpos.replace("data-", "");
        }
        string += " ui-btn-icon-" + (def || "left");
      }
    }
    return string;
  };

  /** Generate a configuration object for a search input
   * @method searchBar
   * @param {object} spec Search bar configuration
   * @return {object} finished search bar config
   **/
  factory.util.searchBarTemplate = function (spec) {
    return {
      "type": "input",
      "direct": {"id": spec.search_id, "className": "translate"},
      "attributes": {
        "data-action": "search",
        "data-enhanced": "true",
        "placeholder": spec.text,
        "data-icon": "search",
        "data-action-btn": "true",
        "data-type": "search",
        "type": "search"
      },
      "logic": {
        "clear": "true",
        "action": "search",
        "data-i18n": spec.text_i18n ?
            ("[placeholder]" + spec.text_i18n) : null
      }
    };
  };

  /** Generate a seach bar (controlbar)
   * @method searchBar
   * @param {object} spec Pagination configuration
   * @return {object} finished config object
   **/
  factory.util.searchBar = function (spec) {
    var i, element_list, search_id, props, spec_list;

    search_id = spec.search_id = "search_" + util.uuid();
    props = spec.slot ? {"slot": spec.slot} : {};
    props.class_list = spec.class_list || "";
    element_list = [factory.util.searchBarTemplate(spec)];
    spec_list = spec.info_list || [];

    for (i = 0; i < spec_list.length; i += 1) {
      element_list.push({
        "type": "div",
        "direct": {"className": "info"},
        "attributes": {"data-info": spec_list[i]}
      });
    }

    return {
      "config": {
        "generate": "widget",
        "type": "controlbar",
        "property_dict": props,
        "children": element_list
      },
      "id": search_id
    };
  };

  /** Generate a pagination toolbar (controlbar)
   * @method paginationBar
   * @param {object} spec Pagination configuration
   * @return {object} finished config object
   **/
  // TODO: make this a real widget with methods!
  factory.util.paginationBar = function (spec) {
    var n, option, props, option_list, config, len, opts, pointer;

    option_list = [{"value": "", "text": "", "text_i18n": ""}];
    opts = spec.option_list || [];
    props = spec.slot ? {"slot": spec.slot} : {};
    props.class_list = spec.class_list || "";

    for (n = 0, len = opts.length; n < len; n += 1) {
      option = opts[n];
      option_list.push({
        "value": option.value,
        "text": option.text,
        "text_i18n": option.text_i18n
      });
    }

    config = {
      "generate": "widget",
      "type": "controlbar",
      "property_dict": props,
      "children": [{
        "generate": "widget",
        "type": "controlgroup",
        "property_dict": {"direction": "horizontal"},
        "children": []
      }]
    };
    pointer = config.children[0].children;

    // first
    if (!spec.single) {
      config.children[0].property_dict.class_list = "ui-pagination-single";
      pointer.push({
        "type": "a",
        "direct": {"className": "action", "href": "#"},
        "attributes": {
          "data-i18n": "global_dict.first",
          "data-action": "first",
          "data-icon": "step-backward",
          "data-iconpos": "notext"
        },
        "logic": {"text": "First"}
      });
    }

    // always add previous
    pointer.push({
      "type": "a",
      "direct": {"className": "action", "href": "#"},
      "attributes": {
        "data-i18n": "global_dict.previous",
        "data-action": "prev",
        "data-icon": "backward",
        "data-iconpos": "notext"
      },
      "logic": {"text": "Previous"}
    });

    // select
    if (len > 0) {
      pointer.push({
        "type": "select",
        "direct": {"id": "select_" + util.uuid(), "className": "action"},
        "attributes": {
          "data-icon": "bars",
          "data-action": "limit",
          "data-iconpos": "notext"
        },
        "logic": {"options": option_list}
      });
    }

    // always add next
    pointer.push({
      "type": "a",
      "direct": {"className": "action", "href": "#"},
      "attributes": {
        "data-i18n": "global_dict.next",
        "data-action": "next",
        "data-icon": "forward",
        "data-iconpos": "notext"
      },
      "logic": {"text": "Next"}
    });

    // only add last button, if total is available
    if (!app.storage_dict.property_dict.skip_total_records && !spec.single) {
      pointer.push({
        "type": "a",
        "direct": {"className": "action", "href": "#"},
        "attributes": {
          "data-i18n": "global_dict.last",
          "data-action": "last",
          "data-icon": "step-forward",
          "data-iconpos": "notext"
        },
        "logic": {"text": "Last"}
      });
    }

    return config;
  };

  // TODO: remove, should be handled by content.set
  // TODO: why is this not done on click, when we can get the href > element
  // from the link attribute?
  /**
   * Load content into a dynamic element
   * @method generateDynamicContents
   * @param {obj} object Action object (popupbeforeposition)
   */
  factory.util.generateDynamicContents = function (obj) {
    var element, reference, state, $el;

    element = obj.gadget;
    reference = element.getAttribute("data-reference");
    state = element.getAttribute("data-state");

    // don't reload if same element is opened
    if (state !== reference) {
      if (reference === null) {
        app.util.error("Global Bindings: No element handler");
      } else {
        element.setAttribute("data-state", reference);
        element.setAttribute("data-reference", reference);

        // empty gadget and reload
        // TODO: how to replace in javaScript?
        element.innerHTML = "";

        // fetch content
        storage.fetchConfigAttachment({
          "storage": app.storage_dict.settings,
          "file": app.storage_dict.property_dict.name_dict.gadgets,
          "attachment": reference,
          "pass": undefined
        })
          .then(function (reply) {
            return RSVP.resolve(app.content.set(reply, {}));
          })
          .then(function (content) {
            // translate
            if (i18n) {
              map.actions.translateNodeList(content);
            }
            // append
            element.appendChild(content);
          })
          .then(function () {
            $el = $(element);

            // TODO: un-jQuery eventually...
            switch ($el.data("role")) {
            case "popup":
              $el.popup("reposition", {"positionto": "window"});
              break;
            }
            $el.enhanceWithin();
          })
          .fail(app.util.error);
      }
    }
  };

  // Remove, should be handled elsewhere!
  /**
   * Set pointer on which content to fetch
   * @method setDynamicPointer
   * @param {obj} obj Action Object
   * @param {string} pointer Pointer to set to
   */
  factory.util.setDynamicPointer = function (obj, pointer) {
    // don't do nothing if the popup is already set with this content
    if (obj.gadget.getAttribute("data-reference") === pointer) {
      return false;
    }
    obj.gadget.setAttribute("data-reference", pointer);
  };


  /* ====================================================================== */
  /*                                MAPPING                                 */
  /* ====================================================================== */

  /**
   * Mapping data structure to factory (actions, gadgets, buttons, utilities)
   * @object map
   **/
  map = {};

  /* ********************************************************************** */
  /*                              Map Actions                               */
  /* ********************************************************************** */

  /**
   * Add connection to application standard actions or add own actions here
   * @object map.actions
   **/
  // TODO: this whole thing should be loaded optionally
  // TODO: best would be that a widget loads it's actions
  // TODO: instead of setting links, retrieve URL right away and call
  // content.set..., no need for dynamic content and pointer
  map.actions = {

    /**
     * POST an object
     * @method new
     * @param {object} obj Action Object
     **/
    "new": function (obj) {
      storage.write(obj)
        .then(function (response) {
          app.util.loader("", "status_dict.saved", "check");
          app.navigate(obj, response);
        })
        .fail(function (error) {
          switch (error.status) {
          case 408:
            app.util.loader("", "status_dict.timeout", "clock-o");
            break;
          case 400:
            app.util.loader("", "validation_dict.general", "ban");
            break;
          default:
            app.util.loader("", "status_dict.error", "ban");
            break;
          }
        });
    },

    /**
     * GET an object
     * @method get
     * @param {object} obj Action Object
     **/
    "get": function (obj) {
      storage.fetch(obj);
    },

    /**
     * PUT an object
     * @method update
     * @param {object} obj Action Object
     **/
    "update_bip": function (obj) {
      var element_list, i, len, value;
      if (obj.form) {
        element_list = obj.form.getElementsByTagName("input");
        for (i = 0, len = element_list.length; i < len; i += 1) {
          if (element_list[i].name === "identifier") {
            value = element_list[i].value;
          }
        }
      }

      if (value) {
        storage.fetch({
          "query": {"_id": value},
          "pass": obj.pass || {}
        })
          .then(function (reply) {
            obj.backpack = reply.response.data.rows[0].doc;
          })
          .then(function () {
            return storage.write(obj)
              .then(function (response) {
                app.util.loader("", "status_dict.saved", "check");
                app.navigate(obj, response);
              })
              .fail(function (error) {
                switch (error.status) {
                case 408:
                  app.util.loader("", "status_dict.timeout", "clock-o");
                  break;
                case 400:
                  app.util.loader("", "validation_dict.general", "ban");
                  break;
                default:
                  app.util.loader("", "status_dict.error", "ban");
                  break;
                }
              });
          })
          .fail(app.util.error);

      } else {
        storage.write(obj)
        .then(function (response) {
          app.util.loader("", "status_dict.saved", "check");
          app.navigate(obj, response);
        })
        .fail(function (error) {
          switch (error.status) {
          case 408:
            app.util.loader("", "status_dict.timeout", "clock-o");
            break;
          case 400:
            app.util.loader("", "validation_dict.general", "ban");
            break;
          default:
            app.util.loader("", "status_dict.error", "ban");
            break;
          }
        });
      }
    },

     /**
     * PUT an object
     * @method update
     * @param {object} obj Action Object
     **/
    "done_bip": function (obj) {
      var element_list, i, len, value;
      if (obj.form) {
        element_list = obj.form.getElementsByTagName("input");
        for (i = 0, len = element_list.length; i < len; i += 1) {
          if (element_list[i].name === "identifier") {
            value = element_list[i].value;
          }
        }
      }

      if (value) {
        storage.fetch({
          "query": {"_id": value},
          "pass": obj.pass || {}
        })
          .then(function (reply) {
            obj.backpack = reply.response.data.rows[0].doc;
          })
          .then(function () {
            return storage.write(obj)
              .then(function (response) {
                alert(i18n.t("global_dict.thanks"));
                app.navigate(obj, response);
              })
              .fail(function (error) {
                switch (error.status) {
                case 408:
                  app.util.loader("", "status_dict.timeout", "clock-o");
                  break;
                case 400:
                  app.util.loader("", "validation_dict.general", "ban");
                  break;
                default:
                  app.util.loader("", "status_dict.error", "ban");
                  break;
                }
              });
          })
          .fail(app.util.error);

      } else {
        storage.write(obj)
        .then(function (response) {
          alert(i18n.t("global_dict.thanks"));
          app.navigate(obj, response);
        })
        .fail(function (error) {
          switch (error.status) {
          case 408:
            app.util.loader("", "status_dict.timeout", "clock-o");
            break;
          case 400:
            app.util.loader("", "validation_dict.general", "ban");
            break;
          default:
            app.util.loader("", "status_dict.error", "ban");
            break;
          }
        });
      }
    },

    /**
     * PUT an object
     * @method update
     * @param {object} obj Action Object
     **/
    "update": function (obj) {
      storage.write(obj)
        .then(function (response) {
          app.util.loader("", "status_dict.saved", "check");
          app.navigate(obj, response);
        })
        .fail(function (error) {
          switch (error.status) {
          case 408:
            app.util.loader("", "status_dict.timeout", "clock-o");
            break;
          case 400:
            app.util.loader("", "validation_dict.general", "ban");
            break;
          default:
            app.util.loader("", "status_dict.error", "ban");
            break;
          }
        });
    },

     /**
     * Same as put but since there is no time to make dyno interaction, cheat!
     * @method update
     * @param {object} obj Action Object
     **/
    "update_custom": function (obj) {
      storage.write(obj)
        .then(function () {
          var i, len, dyno_list, dyno, promise_list, dump;

          // clear active page, because we need to reload
          dump = document.querySelector("div.ui-content");
          util.deleteChildren(dump);

          // refresh dynos that are left!
          dyno_list = document.querySelectorAll("div.dyno");

          // set first page to reload and clean it up!
          delete app.deeplink_flag;

          promise_list = [];
          for (i = 0, len = dyno_list.length; i < len; i += 1) {
            dyno = dyno_list[i];
            // update gadgets
            promise_list[i] = app.content.set(
              {
                "portal_type_source": dyno.state.type,
                "portal_type_title": dyno.state.title,
                "property_dict": util.mergeObject(
                  {"dynamic": true},
                  dyno.state.dyno_dict
                ),
                "scheme": dyno.state.scheme
              },
              {
                "reference": dyno.id,
                "href": dyno.state.href,
                "fragment_list": dyno.state.fragment_list,
                "layout_level": dyno.state.layout_level
              },
              false
            )
              .fail(app.util.error);
          }
          return RSVP.all(promise_list)
            .then(function () {
              app.util.loader("", "status_dict.saved", "check");
              //app.navigate(obj, response);
            })
            .fail(app.util.error);
        })
        .then(app.setPageBindings)
        .fail(function (error) {
          switch (error.status) {
          case 408:
            app.util.loader("", "status_dict.timeout", "clock-o");
            break;
          case 400:
            app.util.loader("", "validation_dict.general", "ban");
            break;
          default:
            app.util.loader("", "status_dict.error", "ban");
            break;
          }
        });
    },

    /**
     * Look up single value from dict
     * @method translateLookup
     * @param {sting} path Lookup path
     * @return {string} lookup value in current language
     **/
    "translateLookup": function (path) {
      if (i18n) {
        return i18n.t(path);
      }
      app.util.error("Translate NodeList - i18n not defined");
      return "";
    },

    /**
     * Translate a nodeList (which can't be passed into i18n).
     * By using this the translations can be done BEFORE appending to the DOM
     * saving full DOM traversal and extensive jQuery-ing
     * @method translateNodeList
     * @param {object} nodeList Nodelist to translate
     * @param {boolean} single Single element
     **/
    // TODO: don't call this so often on init
    "translateNodeList": function (nodeList, single) {
      var i, l, element, lookup, targets, target, route_text, elements, len;

      elements = single ? [nodeList] : nodeList.querySelectorAll(".translate");

      if (i18n) {
        for (i = 0, len = elements.length; i < len; i += 1) {
          element = elements[i];
          lookup = element.getAttribute("data-i18n");

          // JQM inputs > this avoids having to call checkboxRadio("refresh")!
          if (element.tagName === "INPUT") {
            switch (element.type) {
            case "submit":
            case "reset":
            case "button":
              route_text = true;
              break;
            }
          }

          if (lookup) {
            if (lookup.indexOf(";") > -1) {
              targets = lookup.split(";");
            } else {
              targets = [lookup];
            }
            for (l = 0; l < targets.length; l += 1) {
              target = targets[l].split("]");

              // all hail performance
              switch (target[0]) {
              case "[placeholder":
              case "[alt":
              case "[title":
                element.setAttribute(target[0].substr(1), i18n.t(target[1]));
                break;
              case "[value":
                if (route_text) {
                  element.previousSibling.textContent = i18n.t(target[1]);
                }
                element.value = i18n.t(target[1]);
                break;
              case "[parent":
                element.parentNode.childNodes[0].textContent =
                    i18n.t(target[1]);
                break;
              case "[node":
                element.childNodes[0].textContent = i18n.t(target[1]);
                break;
              case "[html":
                // NOTE: slow
                element.innerHTML = i18n.t(target[1]);
                break;
              default:
                // NOTE: empty - setting .translate on a wrapper will empty it
                util.deleteChildren(element);
                element.appendChild(
                  document.createTextNode(i18n.t(target[0]))
                );
                break;
              }
            }
          }
          // handle select > span!
          if (element.tagName === "OPTION") {
            if (element.selected && element.parentNode.previousSibling) {
              element.parentNode.previousSibling.textContent =
                element.textContent;
            }
          }
        }
      } else {
        app.util.error("Translate NodeList - i18n not defined");
      }
    },

    /**
     * Translate the active page
     * @method translate
     * @param {object} obj Action Object
     * @param {string} language Language to change to
     * @param {boolean} init Flag for inital translation
     **/
    // TODO: figure out a way to translate per gadget!
    // TODO: figure out if and how to translate meta easily
    "translate": function (obj, language, init) {
      var class_list,
        lookup,
        new_language,
        tag = obj.element.tagName,
        current_language = $.i18n.lng(),
        shortcut = function (language) {
          switch (language) {
          case "fr-FR":
            return "flag-fr";
          case "en-EN":
            return "flag-en";
          }
        };

      // no way to set
      if (current_language !== language || init) {

        if (tag === "SELECT") {
          class_list = obj.element.parentNode.className.replace(
            shortcut(current_language),
            shortcut(language)
          );
          obj.element.parentNode.className = class_list;
          new_language = language;
        } else {
          new_language = current_language;
        }

        $.i18n.setLng(new_language, function () {
          map.actions.translateNodeList(document);

          // also translate page header
          lookup = util.getHeader()
            .getElementsByTagName("H1")[0]
            .getAttribute("data-i18n");

          if (lookup) {
            document.title = map.actions.translateLookup(lookup);
          }
        });
      }
    },

    /**
     * Generic pagination method changing number of records displayed
     * @method limit
     * @param {object} obj Action Object
     * @param {string} value Value of select
     **/
    "limit": function (obj, value) {
      app.paginate(obj, "limit", value);
    },

    /**
     * Generic pagination method going to first page
     * @method first
     * @param {object} obj Action Object
     **/
    "first": function (obj) {
      app.paginate(obj, "first");
    },

    /**
     * Generic pagination method going to last page
     * @method last
     * @param {object} obj Action Object
     **/
    "last": function (obj) {
      app.paginate(obj, "last");
    },

    /**
     * Generic pagination method going backward
     * @method prev
     * @param {object} obj Action Object
     **/
    "prev": function (obj) {
      app.paginate(obj, "prev");
    },

    /**
     * Generic pagination method going forward
     * @method next
     * @param {object} obj Action Object
     **/
    "next": function (obj) {
      app.paginate(obj, "next");
    },

    /**
     * Switch sorting state and trigger update of gadget
     * @method sort
     * @param {object} obj Action Object
     **/
    // TODO: integrate with app/app.sort, do we really need 2 methods
    "sort": function (obj) {

      // 1 or 5 state sorting (none|asc_abc|desc_abc|asc_123|desc_123)
      switch (obj.element.getAttribute("data-direction")) {
      case "desc_abc":
        app.sort(
          obj,
          "desc_123",
          "sort-by-alphabet-alt",
          "sort-by-order-alt"
        );
        break;
      case "desc_123":
        app.sort(obj, "asc_abc", "sort-by-order-alt", "sort-by-alphabet");
        break;
      case "asc_abc":
        app.sort(obj, "asc_123", "sort-by-alphabet", "sort-by-order");
        break;
      case "asc_123":
        app.sort(obj, undefined, "sort-by-order", "sort");
        break;
      default:
        app.sort(obj, "desc_abc", "sort", "sort-by-alphabet-alt");
        break;
      }
    },

    /**
     * Selecting single checkbox
     * @method check
     * @param {object} obj Action Object
     **/
    "check": function (obj) {
      app.check(obj, undefined);
    },

    /**
     * Select all visible rows (default)
     * @method check_all_visible
     * @param {object} obj Action Object
     **/
    "check_all_visible": function (obj) {
      app.check(obj, false);
    },

    /**
     * Select ALL rows (visible and not visible)
     * @method check_all
     * @param {object} obj Action Object
     **/
    // NOTE: checkbox has 3 states (this hijacks indeterminate state!!!)
    // NOTE: for checkboxes, label may be in front or after the checkbox
    "check_all": function (obj) {
      var checkbox = obj.element,
        label = checkbox.previousSibling || checkbox.nextSibling;

      if (checkbox.checked) {
        if (checkbox.indeterminate === false) {
          checkbox.setAttribute("flag", true);
        }
      } else {
        if (checkbox.getAttribute("flag")) {
          checkbox.removeAttribute("flag");
          checkbox.indeterminate = true;
          checkbox.checked = true;

          label.className = label.className.replace(
            util.filterForClass("ui-checkbox-on"),
            " ui-icon-globe"
          );
        } else {
          checkbox.indeterminate = false;
          label.className = label.className.replace(
            util.filterForClass("ui-icon-globe"),
            ""
          );
        }
      }
      // create visual and state
      app.check(obj, true);
    },

    /**
     * Search (default)
     * @method search
     * @param {object} obj Action Object
     **/
    "search": function (obj) {
      app.search(obj);
    }
  };

  /* ********************************************************************** */
  /*                         Mapper Generic Element                         */
  /* ********************************************************************** */
  /**
   * Calls respective mappers depending on element passed
   * @method element
   * @param {object} element Element JSON configuration
   * @param {object} wrapper Object holding mapping information
   * @param {integer} i Counter
   * @return {object} promise object
   **/
  // TODO: allow to run multiple mappers eg formItem + tableRow
  map.element = function (element, wrapper, i) {
    if (wrapper.child_mapper) {
      if (element.scheme) {
        // NOTE: chaining allows the mapper to make additional async requests!
        return map[wrapper.child_mapper]({
          "item": element,
          "wrapper": wrapper,
          "count": i
        })
          .then(function (response) {
            // may still need a widget constructor to run over mapped response
            if (wrapper.child_constructor) {
              return RSVP.resolve(
                wrapper.child_constructor({
                  "item": response,
                  "wrapper": wrapper,
                  "count": i
                })
              );
            }
            // no need for constructor
            return RSVP.resolve(response);
          })
          .fail(app.util.error);
      }
      return RSVP.resolve(wrapper.child_constructor({
        "item": element,
        "wrapper": wrapper,
        "count": i
      }));
    }

    // NOTE: when loading content via href, element is set to undefined
    // TODO: Why? ^^^^^^
    // NOTE: fetch subordinate fields
    if (element && element.needs_subordination) {
      return app.util.setSubordinate(element, wrapper);
    }
    return RSVP.resolve(element || wrapper.inherit);
  };

  /* ********************************************************************** */
  /*                         Mapper table item                              */
  /* ********************************************************************** */
  /**
   * Map records to factory widget table item
   * @method tableItem
   * @param {object} spec Object containing element, parent and counter
   * @return {object} mapped object
   **/
  // TODO: multiple header rows!
  // TODO: action_menu input + form?
  // TODO: how to handle translations of record texts?
  // TODO: subordinate handling
  // TODO: pick translations from field_dict for header!
  map.tableItem = function (spec) {
    var j, k, segment, row, cell, id, quirk_dict, record, new_item, item;

    quirk_dict = spec.wrapper.property_dict;
    item = spec.item;
    record = item.doc || {};
    id = record[quirk_dict.link_identifier] || record._id || item._id;
    new_item = {};

    // id
    new_item.id = id;

    // link
    if (quirk_dict.link) {
      new_item.href = app.util.generateLink(quirk_dict, id);
      new_item.external = quirk_dict.external;
    }

    // loop scheme
    for (j = 0; j < item.scheme.length; j += 1) {
      segment = item.scheme[j];
      row = [];

      // loop field_list = cells
      for (k = 0; k < segment.field_list.length; k += 1) {
        cell = util.mergeObject(
          new_item,
          util.cloneObject(segment.field_list[k])
        );

        // custom header (!) cells have text/i18n defined
        if (!cell.custom) {
          cell.text = util.generateText(
            record[cell.value || cell.field],
            cell,
            record
          );
          cell.text_i18n = record.text_i18n || null;
        }
        row.push(cell);
      }
    }

    return RSVP.resolve(row);
  };

  /* ********************************************************************** */
  /*                         Mapper list item                               */
  /* ********************************************************************** */
  /**
   * Map records to factory widget listview and carousel
   * @method listItem
   * @param {object} spec Object containing element, parent and counter
   * @return {object} mapped object
   **/
  // TODO: move radio/check into property_dict and item? or do same on tableItem
  // TODO: not nice & find better handling for text elements and i18n tags
  // TODO: subordinated fields should have links, not id only!!!
  // NOTE: make sure there is no "id" flying through here!!! only _id
  map.listItem = function (spec) {
    var quirk_dict, section, label, j, k, field, record, new_item,
      id, key, translation_fields, item, relation, promise_list, pass, count;

    promise_list = [];
    translation_fields = "titletextlabel";
    quirk_dict = spec.wrapper.property_dict;
    item = spec.item;
    record = item.doc;
    id = record[quirk_dict.link_identifier] || record._id || item._id;
    new_item = {};
    count = 0;

    // radio
    if (quirk_dict.radio) {
      label = true;
      new_item.right = [{"type": "radio", "action": "check"}];
    }

    // checkbox
    if (quirk_dict.check) {
      label = true;
      new_item.right = [{"type": "check", "action": "check"}];
    }

    // link
    if (quirk_dict.link) {
      label = true;
      new_item.href = app.util.generateLink(quirk_dict, id);
      new_item.external = quirk_dict.external;
    }

    // id
    if (label) {
      new_item.id = id;
    }

    // loop scheme sections
    for (j = 0; j < item.scheme.length; j += 1) {
      section = item.scheme[j];

      // loop scheme fields
      for (k = 0; k < section.field_list.length; k += 1) {
        field = section.field_list[k];
        key = field.value || field.field || "";
        relation = key.split("subordinate_");
        pass = {
          "record": record,
          "position": section.position,
          "key": key,
          "field": field,
          "text_element": translation_fields.indexOf(key) > -1
        };

        // fetch values of subordinate fields via query or flux/memory
        if (relation.length > 1) {
          pass.relation = record[key];
          pass.subordinate = relation[1];

          promise_list[count + k] = storage.subordinate(pass);
        } else {
          pass.value = util.generateText(record[key], field, record);
          promise_list[count + k] = RSVP.resolve(pass);
        }
      }
      count += k;
    }

    return RSVP.all(promise_list)
      .then(function (response_list) {
        var l, len, response, obj, new_setter, pos;

        for (l = 0, len = response_list.length; l < len; l += 1) {
          response = response_list[l];
          pos = response.position;
          obj = {};
          new_item[pos] = new_item[pos] || [];

          // text/label/... all stuff translateable
          if (response.text_element) {
            obj[response.key + "_18n"] = record[response.key  + "_i18n"];
            new_setter = key;
          } else {
            new_setter = "value";
          }

          obj[new_setter] = response.value;
          new_item[pos].push(util.mergeObject(response.field, obj));
        }
        return new_item;
      })
      .fail(app.util.error);
  };

  /* ********************************************************************** */
  /*                         Mapper form element                            */
  /* ********************************************************************** */
  /**
   * Map records to factory widget form item
   * @method formItem
   * @param {object} spec Object containing element, parent and counter
   * @return {object} mapped object
   **/
  // TODO: should a span still have full structure (label/container)?
  // TODO: should we wrap in span_12 here. why do we wrap at all, do with CSS
  // TODO: subordinate value
  map.formItem = function (spec) {
    var helper, getHelp, j, k, segment, section, field, setValue, root, veto,
      textarea_value, override_value, value, field_value, validation_list,
      class_list, el, type, clear, alt_name, fetch_items, pass,
      field_list, l, m, section_list, container, disabled, readonly, item;

    item = spec.item;
    getHelp = function (prop) {
      return {
        "generate": "widget",
        "type": "controlbar",
        "property_dict": prop,
        "children": []
      };
    };

    // TODO: remove this crap or make it generic...
    // set = setter_list ... JSLINT
    setValue = function (setter) {
      var n, len, set_value, set_flux, set, val, set_reference, logic;

      logic = setter.logic;
      if (logic) {
        set_reference = logic.setReference;
        set_value = logic.setValue;
        val = logic.lookupValue;
        set = logic.setters;
        set_flux = logic.setFlux;

        if (set) {
          len = set.length;
          for (n = 0; n < len; n += 1) {
            if (val) {
              // FUÂ§$%&/NG HAL
              logic[set[n]] = item.doc[val[n][0]][val[n][1]][val[n][2]];
            } else {
              logic[set[n]] = item.doc[set_value];
            }
          }
        } else if (set_value) {
          setter.direct.value = item.doc[set_value];
        // software_instance_status again...
        }

        if (set_reference) {
          setter.attributes["data-reference"] = item.reference;
        }

        if (set_flux) {
          if (flux && flux._temp) {
            setter.direct.value = flux._temp[set_flux];
            delete flux._temp[set_flux];
          } else {
            // translation!?
            setter.direct.value = logic.noFlux;
          }
        }
      }
    };

    // NOTE: all children of a form will be passed through here, these can
    // be ready HTML constructor objects, widgets or form elements
    // requiring mapping (custom or based on element.field_dict)
    if (!!item.direct) {
      setValue(item);
    } else if (!!item.generate) {
      // NOTE: we must declare wrap for non-form elements here, because they
      // don't belong into any widget
      // NOTE: persist if set!
      container = getHelp({
        "wrap": "section",
        "persist": item.property_dict.persist,
        "class_list": "span_" + (item.property_dict.wrap || 1)
      });
      container.children.push(item);
    } else {
      // NOTE: since we are creating 2 levels in the DOM tree (fragment &
      // sections), the normal inheritance will break, because a wrapper
      // will be missing state. Therefore we add the state manually here,
      // TODO: don't do 2 level stuff.... and complain later when debugging
      helper = getHelp({
        "wrap": "fragment",
        "state": spec.wrapper.property_dict.state
      });
    }

    // nothing to do, pass back directly
    if (!helper) {
      return RSVP.resolve(container || item);
    }

    section_list = [];
    // Need mapping, fill helper with sections filled with (mapped) fields

    // loop scheme
    for (j = 0; j < item.scheme.length; j += 1) {
      segment = item.scheme[j];
      field_list = [];
      type = null;

      // loop scheme field_list
      for (k = 0; k < segment.field_list.length; k += 1) {
        field = segment.field_list[k];
        clear = null;
        textarea_value = null;
        pass = undefined;
        fetch_items = null;
        type = undefined;

        // "handmade" fields (HTML constructor)
        // TODO: too generic?
        if (field.type || field.custom) {
          setValue(field);
          pass = {"push": field};
        }

        // pass HTML fields
        if (field.logic && field.logic.skip) {
          pass = {"push": field};
        }

        // fields that needing mapping (custom or based on dict)
        if (!pass) {
          root = item.field_dict[field.field];
          veto = util.mergeObject(
            field.overrides || {},
            {"properties": {}, "widget": {}}
          );

          value = item.doc[field.setValue || field.field];

          // flag false/missing field_dict
          if (!root) {
            app.util.error("mapFormField: Missing field definition");
          }

          // override may be set to 0, need to set to "0"...
          override_value = veto.widget.default_value;

          // actual value being set on the field
          field_value = value ||
              (override_value === "0" ? "0" : (override_value ||
                  root.widget.default_value ||
                      undefined));

          // easy way out if not enabled
          // TODO: should be full field setup with label... and span
          if (veto.properties.enabled === false ||
                root.properties.enabled === false) {
            pass = {
              "push": {
                "type": "span",
                "direct": {"className": "static"},
                "attributes": {},
                "logic": {"text": field_value}
              }
            };
          } else if (veto.type === "ImageField" || root.type === "ImageField") {
            pass = {
              "push": {
                "type": "img",
                "direct": {"src": field_value, "alt": ""},
                "attributes": {"skip": true}
              }
            };
          }

          // check for options (select elements), which need to be fetched
          fetch_items = veto.widget.items || root.widget.items || null;
          alt_name = veto.widget.alternate_name || root.widget.alternate_name;
          validation_list = "";
          class_list = "";
          disabled = (veto.properties.enabled === false ||
              root.properties.enabled === false);
          readonly = (veto.properties.editable === false ||
              root.properties.editable === false);

          // validation (assumes validval is used!)
          // NOTE: method must be present or added to validation handler
          if (veto.properties.external_validator ||
              root.properties.external_validator) {
            validation_list += (veto.properties.external_validator ||
              root.properties.external_validator);
          }

          // required fields
          if (root.properties.required === true) {
            if (veto.properties.required === undefined ||
                veto.properties.required === true
                ) {
              class_list += "required ";
            }
          }

          // maximum_length
          if (veto.properties.maximum_length ||
              root.properties.maximum_length) {
            validation_list += "|max_length:" +
              (veto.properties.maximum_length ||
              root.properties.maximum_length) + "&truncate:" +
              (veto.properties.truncate || root.properties.truncate);
          }

          // type-related validation, element and type definition
          switch (veto.type || root.type) {
          //case "ImageField":
          //  break;
          case "StringField":
          case "RelationStringField":
          case "IntegerField":
            el = "input";
            type = "text";
            clear = true;
            break;
          case "PasswordField":
            el = "input";
            type = "password";
            clear = true;
            break;
          case "CheckboxField":
            el = "input";
            type = "checkbox";
            break;
          case "RadioField":
            el = "input";
            type = "radio";
            break;
          case "MultiListField":
          case "ListField":
          case "ParallelListField":
            el = "select";
            break;
          case "DateTimeField":
            el = "input";
            clear = true;
            // NOTE: generate datePicker if no support for type="date"
            type = "date";
            validation_list += "|type:dateTime" + "|start:" +
              (veto.properties.start_datetime ||
              root.properties.start_datetime) + "|end:" +
              (veto.properties.end_datetime ||
              root.properties.end_datetime) + "|null:" +
              (veto.properties.allow_empty_datetime ||
              root.properties.allow_empty_datetime);
            break;
          case "TextareaField":
            el = "textarea";
            type = null;
            validation_list += "|max_lines:" +
              (veto.properties.maximum_lines ||
              root.properties.maximum_lines) + "|max_characters:" +
              (veto.properties.maximum_characters ||
              root.properties.maximum_characters) +
              "|maximum_length_of_line:" +
              (veto.properties.maximum_length_of_line ||
              root.properties.maximum_length_of_line);
            textarea_value = field_value;
            field_value = null;
            break;
          case "EmailField":
            validation_list += "|type:email";
            el = "input";
            clear = true;
            type = "email";
            break;
          }

          if (validation_list === "") {
            validation_list = undefined;
          }

          // NOTE: type (radio...) set in attributes, can be NULL (textarea)
          // NOTE: by default we wrap and label forms
          pass = {
            "push": {
              "type": el,
              "wrap": veto.wrap || true,
              "label": veto.label || true,
              "direct": {
                "id": item.reference + "_" +
                    (veto.widget.id || root.widget.id),
                "className": class_list + " " + (veto.widget.css_class ||
                    root.widget.css_class || "")
              },
              "attributes": {
                "data-enhanced": "true",
                "type": (veto.widget.hidden || root.widget.hidden) === true ?
                    "hidden" : (type || null)
              },
              "logic": {
                "data-vv-validations": validation_list || undefined,
                "extra": veto.widget.extra || root.widget.extra || undefined,
                "name": alt_name ? item.reference + "_" + alt_name : null,
                "size": veto.widget.display_width ||
                    root.widget.display_width || veto.widget.size ||
                        root.widget.size || undefined,
                "rows": veto.widget.width || root.widget.width || undefined,
                "cols": veto.widget.height || root.widget.height ||
                    undefined,
                "disabled": disabled ? true : null,
                "value": field_value,
                "text": textarea_value,
                "readonly": readonly ? "readonly" : undefined,
                "options": null,
                "label": veto.widget.title || root.widget.title,
                "label_i18n": veto.widget.title_i18n ||
                    root.widget.title_i18n,
                "title": veto.widget.description || root.widget.description,
                "title_i18n": veto.widget.description_i18n ||
                  root.widget.description_i18n,
                "clear": (readonly || disabled) ? null : (clear || null)
              }
            }
          };

          // NOTE: we can't remove type from attributes, but must delete
          // it if it's null, otherwise code is not valid (Opera chokes)
          if (pass.push.attributes.type === null) {
            delete pass.push.attributes.type;
          }

        }

        // need to set position here to also include custom fields
        pass.position = segment.position === "center" ? 1 : 2;

        // fetch dynamic options
        if (fetch_items === null) {
          pass.skip = true;
        }

        // NOTE: single runner per call, no need to pass things
        // NOTE: we store in scramble because it should be a portal_type!
        // TODO: make properly...
        field_list[k] = storage.fetchConfigAttachment({
          "storage": app.storage_dict.settings,
          "file": "scrambled",
          "attachment": fetch_items,
          "pass": pass
        }).then(function (answer) {
          var o, p, new_value, option, len, loaded_options, el_type,
            radio_check, clone, clone_opt, group, push, group_id;

          loaded_options = answer.response;

          // need to handle multi radio/checkbox groups... and selects????
          if (loaded_options) {

            push = answer.pass.push;
            push.logic.options = util.parse(loaded_options);
            new_value = push.logic.value;
            len = loaded_options.length;
            el_type = push.attributes.type;
            radio_check = (el_type === "radio" || el_type === "checkbox");

            // TODO: set value here? this is a mapper??!
            if (new_value) {
              for (o = 0; o < len; o += 1) {
                option = loaded_options[o];
                if (option.value === new_value) {
                  option.checked = true;
                  option.selected = "selected";
                }
              }
            }

            // clone-wars... make a checkbox with fieldlabel
            if (len > 0 && radio_check) {
              group_id = util.uuid();
              group = {
                "generate": "widget",
                "type": "controlgroup",
                "property_dict": {
                  "label": {
                    "text": push.logic.label,
                    "text_i18n": push.logic.label_i18n,
                    "title": push.logic.title,
                    "title_i18n": push.logic.title_i18n
                  }
                },
                "children": []
              };

              for (p = 0; p < len; p += 1) {
                clone_opt = loaded_options[p];
                clone = {
                  "type": "input",
                  "logic": util.cloneObject(push.logic),
                  "attributes": util.cloneObject(push.attributes),
                  "direct": util.cloneObject(push.direct)
                };
                clone.direct.id += "_" + p;
                if (clone_opt.checked) {
                  clone.direct.checked = true;
                }
                clone.attributes["data-vv-validationgroup"] = group_id;
                clone.logic.label = clone_opt.text;
                clone.logic.label_i18n = clone_opt.text_i18n;
                clone.logic.value = clone_opt.value;
                clone.attributes["data-i18n"] = clone_opt.text_i18n;
                switch (p) {
                case 0:
                  clone.position = len === 1 ? null : true;
                  break;
                case len - 1:
                  clone.position = false;
                  break;
                }
                group.children.push(clone);
              }

              // replace original response (since we cloned push!)
              answer.pass.push = group;
            }
          }

          return answer.pass;
        })
          .fail(app.util.error);
      }

      // NOTE: must set property_dict on the controlbar in order to inherit
      // dynamic data to the contained field elements
      section_list[j] = RSVP.all(field_list)
        .then(function (field_response_list) {
          section = getHelp({
            "wrap": "section",
            "class_list": "span_" + (field_response_list[0].position)
          });
          for (l = 0; l < field_response_list.length; l += 1) {
            section.children.push(field_response_list[l].push);
          }
          return RSVP.resolve(section);
        })
        .fail(app.util.error);
    }

    return RSVP.all(section_list)
      .then(function (section_response_list) {
        for (m = 0; m < section_response_list.length; m += 1) {
          helper.children.push(section_response_list[m]);
        }
        // HACK for Thierry
        helper.reference = helper.property_dict.reference =
            spec.wrapper.reference;

        return RSVP.resolve(helper);
      })
      .fail(app.util.error);
  };


  /* ====================================================================== */
  /*                                STORAGE                                 */
  /* ====================================================================== */

  // generate storage object with placeholders
  storage = {
    "empty_obj": {},
    "arr": [],
    "span": document.createElement("span")
  };

  /**
   * Get a subordinate record and store in flux in case multiple properties
   * of this object are requested
   * @method subordinate
   * @param {object} spec Current record
   * @return {promise}
   */
  storage.subordinate = function (spec) {
    var output;

    // fetch from cache or query
    if (flux.active_record[spec.relation]) {
      output = flux.active_record[spec.relation];
    } else {
      output = flux.active_record[spec.relation] = storage.fetch({
        "pass": spec,
        "query": {"_id": spec.relation}
      });
    }

    return output.then(function (answer) {
      var data = answer.response.data;
      // set subordinate value
      if (data.total_rows > 0) {
        spec.value = data.rows[0].doc[spec.subordinate];
      }

      // NOTE: delete here, otherwise impossible to track. This will still
      // work cause subsequent requests will fire async and be set from flux
      delete flux.active_record[spec.relation];
      return spec;
    }).fail(app.util.error);

  };

  /**
   * Fetch data from storage = GET or ALLDOCS
   * @method fetch
   * @param {object} obj Action object
   */
  // TODO: convert config/url_dict into state and save 50000 assignments
  // TODO: set pointer directly inside data-action.... remove pointer!
  // TODO: jump is bad, it is the lookup for the data to display. rename!
  // TODO: the whole assignments are crap
  // TODO: action_id > this should be triggered via form submit not links!!!
  // TODO: does _id always include portal_type? if link, yes, else no.
  storage.fetch = function (obj) {
    var pass, query, method, mapper, config, answer, lookup, pointer, empty,
      action_id, quirk;

    empty = storage.empty_obj;
    pass = obj.pass;
    quirk = obj.state || pass.config_dict || empty;
    lookup = obj.state || quirk.property_dict || empty;
    query = obj.query || lookup.query || empty;
    pointer = (lookup.url_pointer || empty).jump;
    action_id = ((obj.element || empty).href || "").split("/").pop();

    if (action_id) {
      pointer = undefined;
      query = {"_id": window.decodeURIComponent(action_id)};
    }

    // JSLINT my ...
    // TODO: "hack" for forcing get into allDocs, don't overwrite criteria!
    if (!!pointer) {
      mapper = "values";
      query.select_list = query.select_list || pass.data_dict.field_list;
      query.limit = query.limit || pass.config_dict.initial_query.limit;

    } else if ((query.select_list || storage.arr).length > 0 &&
        !query.include_docs
        ) {
      mapper = "values";

    } else if (!!query._id) {
      mapper = "single_item";
      method = "get";
      config = {
        "_view": quirk.view
      };
    }

    // TODO: remove - pass custom query to storage
    if (pointer && !config) {
      config = {
        "_jump": pointer
      };
    }

    return RSVP.resolve(
      app.storage_dict.items[method || "allDocs"](query, config)
    ).then(function (response) {
      if (response.status === 200) {
        if (action_id) {
          return response;
        }
        answer = storage.mapResponse(response, mapper);
      }
      return {
        "response": answer || response,
        "pass": pass
      };
    }).fail(function (error) {
      switch (error.status) {
      case 408:
        app.util.loader("", "status_dict.timeout", "clock-o");
        break;
      case 400:
        app.util.loader("", "validation_dict.general", "ban");
        break;
      default:
        app.util.loader("", "status_dict.error", "ban");
        break;
      }
      app.util.error(error);
    });
  };

  /**
   * Write to storage = POST or PUT
   * @method write
   * @param {object} obj Action object
   */
  // TODO: storage.add included prefetch of field_items and validation
  // TODO: storage.add deleted identifier if create_new was set
  storage.write = function (obj) {
    var form, data, valid, prefix, config, method, action, pointer;

    form = obj.form;
    pointer = (obj.element || storage.span).getAttribute("data-action");
    action = (obj.state.url_pointer || storage.empty_obj)[pointer];
    config = {};
    prefix = obj.id + "_";
    valid = obj.sample_store || storage.validate(form);
    if (valid) {
      data = obj.sample_data || storage.parseForm(valid, prefix, true);

      // HACK: Thierry
      if (obj.backpack) {
        data = util.mergeObject(obj.backpack, data);
      }

      // TODO: remove, used for update scope, doesn't honor form definition
      if (obj.force_data) {
        config._force_data = true;
      }

      if (data._id || form.identifier) {
        // PUT > set id, method and view
        data._id = data._id || form.identifier.value;
        method = "put";
        config._view = obj.state.view;
      }

      if (pointer) {
        if (action) {
          config._action = action;
        } else {
          // NOTE: this is the actual way to post to ERP5, will fail and only
          // work on localStorage, so add portal_type
          data.portal_type = obj.state.type;
        }
        // in case action is triggered that needs retrival from a reference
        data._reference = obj.state.reference;
      }

      app.util.loader("", "status_dict.saving");

      return RSVP.resolve(
        app.storage_dict.items[method || "post"](data, config)
      );
    }
    $.mobile.loading("show");
    app.util.loader("", "validation_dict.general", "ban");
    return RSVP.reject({"status": 400});
  };

  /**
   * Fetch a configuration file (attachment) from storage. Fallback to
   * disk. Successfull fallback file fetches will be stored in JIO.
   * @method fetchConfigAttachment
   * @param {object} obj parameters & pass to pass to subsequent then()
   * @return {object} object including pass and reply
   */
  // TODO: merge with fetch/write
  storage.fetchConfigAttachment = function (obj) {
    var reply, store;

    // NOTE: obj.pass is undefined for storage etc > add pass = {}
    if (obj.pass && (obj.pass.skip || obj.pass.no_config)) {
      return RSVP.resolve({"pass": obj.pass});
    }

    store = obj.storage;

    return store.getAttachment({
      "_id": obj.file,
      "_attachment": obj.attachment
    })
      .then(function (response) {
        return jIO.util.readBlobAsText(response.data);
      })
      .then(function (answer) {
        reply = util.parse(answer.target.result);
        if (obj.pass) {
          return {
            "response": reply,
            "pass": obj.pass
          };
        }
        return reply;
      },
        function (error) {
          if (error.status === 404 && error.id === obj.file) {
            return storage.load({
              "store": store,
              "file": obj.file,
              "attachment": obj.attachment,
              "pass": obj.pass
            });
          }
          // NOTE: throw to get back in success chain!!!
          throw error;
        });
  };

  /**
   * Load files from disk
   * @method loadFile
   * @param {object} property_dict (including pass)
   * @return {object} object
   */
  // TODO: put/putAttachment should be set somewhere more general!
  // TODO: refactor... such a mess
  storage.load = function (obj) {
    var url, store, response, doc_id;

    url = obj.url || app.property_dict.data + obj.attachment + ".json";

    if (!url) {
      if (obj.optional) {
        return RSVP.all([]);
      }
      app.util.error("load: missing URL to load");
    }

    // fetch file
    return util.ajax({"url": url})
      .then(function (event) {
        response = util.parse(event.target.responseText);

        // no need to store? go to next then()
        if (obj.skip) {
          return RSVP.all([]);
        }

        store = obj.store || app.storage_dict.settings || undefined;
        doc_id = obj.file || "settings";
        if (!store) {
          app.util.error("loadFile: no storage defined");
          return RSVP.all([]);
        }

        return store.put({"_id": doc_id})
          .then(function () {
            return store.putAttachment({
              "_id": doc_id,
              "_attachment": (obj.attachment ||
                  app.storage_dict.property_dict.name_dict.configuration),
              "_data": JSON.stringify(response),
              "_mimetype": "application/json"
            });
          });
      })
      .then(function () {
        if (obj.pass) {
          return {
            "response": response,
            "pass": obj.pass
          };
        }
        return response;
      })
      .fail(app.util.error);
  };

  /**
   * Build a query object based on passed configuration
   * @method parseQuery
   * @param {object} query
   * @param {string} type Portal Type to fetch
   * @param {string} key Parameter to search single column
   * @param {string} value Value to search for across one or all columns
   * @param {object} field_list Items to search across
   * @param {array} restore Initial query identifier (key) and value
   * @return {object} query object
   */
  // WARNING: jIO dependency!
  storage.parseQuery = function (query, type, key, value, field_list, restore) {
    var wrap, query_object, query_clean, default_query, obj, is_value, i, is_id;

    is_value = value && value !== "";
    is_id = query && query.query && query.query._id;

    // "id" = "bar" => THIS SHOULD BE GET!
    if ((is_value && key && key === "_id") || is_id) {
      obj = {"_id": window.decodeURIComponent(is_id ? query.query._id : value)};

    // ALLDOCS
    } else {
      default_query = query || {};
      obj = {"query": ''};
      value = window.decodeURIComponent(value);

      // query string passed? parse it
      if (default_query.query) {
        query_clean = default_query.query.replace(/'/g, '"');
        query_object = jIO.QueryFactory.create(query_clean);
        obj.query = '(';

        // missing portal type?
        if (default_query.skip_type === undefined) {
          if (!util.findKey(query_object, "query_list", "key", "portal_type")) {
            obj.query += 'portal_type: "' + type + '" AND ';
          }
        }

        // NOTE: json only allows single quotes inside double quotes. Reverse
        // and add passed query to querystring
        obj.query += query_clean + ')';
      } else {
        obj.query = '(portal_type: "' + type + '")';
      }

      // set last query, so info_fields can easily be updated
      if (is_value) {
        obj.last = value;
      }

      // set wildcard here
      if (default_query.wildcard_character) {
        obj.wildcard_character = default_query.wildcard_character;
      } else {
        obj.wildcard_character = "%";
      }

      // "foo" = "bar"
      if (is_value && key) {
        obj.query += ' AND (' + key + ':"' + value + '")';
        obj.start = 0;
        obj.items = 1;
        obj.restore = [key, value];

      // search "bar"
      } else if (is_value) {
        // we need to check an existing query for the fields we are already
        // searching. These fields should not be set in the search
        if (field_list && query) {
          wrap = "";
          for (i = 0; i < field_list.length; i += 1) {
            if (!util.findKey(
                query_object || {},
                "query_list",
                "key",
                field_list[i]
              )) {
              wrap += field_list[i] + ': "' +
                  obj.wildcard_character +
                      value +
                          obj.wildcard_character + '" OR ';
            }
          }
          obj.query += ' AND (' + wrap.slice(0, -4) + ')';
        }
      }

      // restore initial query in case we are searching for empty string
      if (restore) {
        obj.query += ' AND (' + restore[0] + ':"' + restore[1] + '")';
      }

      if (default_query.sort_on) {
        obj.sort_on = default_query.sort_on;
      } else {
        obj.sort_on = [];
      }

      // empty string might also be a search criteria!
      if (default_query.include_docs || value !== undefined) {
        obj.include_docs = true;
      }
      if (field_list && field_list.length > 0 &&
          app.storage_dict.property_dict.force_field_definitions) {
        delete obj.include_docs;
        obj.select_list = field_list;
      }

      if (default_query.select_list && default_query.select_list.length > 0) {
        obj.select_list = default_query.select_list;
      }
      if (default_query.limit) {
        obj.start = default_query.limit[0];
        obj.items = default_query.limit[1];
      }

      if (obj.start !== undefined) {
        obj.limit = [obj.start, obj.items];
      } else {
        obj.limit = [];
      }
    }
    return obj;
  };

  /**
   * Make a formData object and replace prefixes
   * @method parseForm
   * @param {object} obj Form to parse
   * @param {string} clean id of the form
   * @param {boolean} plain Whether to return formData or normal data (true)
   * @return {object} FormData object
   */
  storage.parseForm = function (obj, clean, plain) {
    var container, property, value;

    if (plain) {
      container = {};
    } else {
      container = new FormData();
    }

    for (property in obj) {
      if (obj.hasOwnProperty(property)) {
        value = obj[property];
        switch (property) {
        case "undefined":
        case "identifier":
          break;
        default:
          if (plain) {
            container[property.replace(clean, "").replace("select_", "")] =
                value;
          } else {
            container.append(property.replace(clean, ""), value);
          }
          break;
        }
      }
    }

    return container;
  };

  /**
   * Generate an array of fields from a dict
   * @method makeSelectList
   * @param {object} scheme of gadget to retrieve fields from
   * @return {array} select_list
   **/
  storage.makeSelectList = function (scheme) {
    var i, j, field, select_list = [];

    for (i = 0; i < scheme.length; i += 1) {
      for (j = 0; j < scheme[i].field_list.length; j += 1) {
        field = scheme[i].field_list[j];
        if (field.field && select_list.indexOf(field.field) === -1) {
          select_list.push(field.field);
        }
      }
    }
    // if nothing is to be fetched, make sure we return at least the _id
    if (select_list.length === 0 && select_list.indexOf("_id") === -1) {
      select_list.push("_id");
    }
    return select_list;
  };

  /**
   * Convert storage responses into allDocs response format which
   * is used in subsequent methods. Will be called for allDocs(selcect_list)
   * and get!
   * @method mapResponse
   * @param {object} response Response object
   * @param {string} convert Type of conversion required
   * @return {object} "allDocs" response object
   **/
  storage.mapResponse = function (response, convert) {
    var answer, i, record, key, data, pushit, value_dict;

    if (convert) {
      answer = {"data": {"total_rows": null, "rows": []}};
      pushit = function (obj, record) {
        var new_record = {
          "id": record.id,
          "key": record.id,
          "value": {},
          "doc": obj
        };
        if (record._missing) {
          new_record._missing = true;
        }
        if (record._conflict_list) {
          new_record._conflict_list = record._conflict_list;
        }
        return new_record;
      };

      if (convert === "single_item") {
        answer.data.total_rows = 1;
        answer.data.rows.push(pushit(response.data, response));
      } else {
        answer.data.total_rows = response.data.total_rows;
        for (i = 0; i < response.data.total_rows; i += 1) {
          data = {};
          record = response.data.rows[i];
          value_dict = record.value;
          for (key in value_dict) {
            if (value_dict.hasOwnProperty(key)) {
              data[key] = value_dict[key];
            }
          }
          // TODO: verfiy this is valid!
          data._id = record.id;
          answer.data.rows.push(pushit(data, record));
        }
      }
    }

    return answer || response;
  };

  /**
   * validate (a form) before submitting to storage
   * @method validate
   * @return {object} form fields/errors
   **/
  storage.validate = function (form_to_test) {
    var i, pass, anti_spam, valid, test_time, test_empty,
      test_full, form_element, form_elements;

    pass = false;
    anti_spam = document.getElementById(form_to_test.id + "_not_a_secret");
    valid = $(form_to_test).triggerHandler("submitForm");

    if (valid !== false) {

      // spam - thx http://nedbatchelder.com/text/stopbots.html
      if (anti_spam) {
        // fill form in < 1sec = spam
        if (Date.now() - parseInt(anti_spam.getAttribute("data-created"), 10)
            > 1000) {
          test_time = true;
        }

        // one empty, one filled secured field
        form_elements = form_to_test.getElementsByTagName("input");
        for (i = 0; i < form_elements.length; i += 1) {
          form_element = form_elements[i];
          if (util.testForString("secure_form", form_element.className)) {
            if (form_element.value === "") {
              test_empty = true;
            }
            if (form_element.value !== "") {
              test_full = true;
            }
          }
        }

        // can we pass
        if (test_empty && test_full && test_time) {
          pass = true;
        }

      // skip spam testing
      } else {
        pass = true;
      }

      // DONE and ...?
      if (pass === true) {
        return valid;
      }
      return undefined;
    }
    return undefined;
  };

  /**
   * remove an item from a storage
   * @method remove
   * @param {object} config Action Object
   **/
  storage.remove = function (config) {
    var store, items, i, kill;

    app.util.loader("", "status_dict.removing");

    store = storage.items;
    kill = config.state.query.last || config.state.query._id;

    // TODO: find a generic way to determine the id of the element to
    // be removed, in a single item gadget it should be the underlying id,
    // in a multi item gadget, we need to select
    // last will be id of element displayed

    if (kill) {
      items = [kill];
    } else {
      app.util.error("No items for deletion defined");
    }

    if (store && items.length > 0) {
      for (i = 0; i < items.length; i += 1) {
        store.remove({"_id": items[i]})
          .then(function () {
            if (config.state.callback) {
              $.mobile.changePage(config.state.callback);
            }
          })
          .fail(app.util.error);
      }
    }
  };

  /* ====================================================================== */
  /*                               FLUX                                     */
  /* ====================================================================== */
  // NOTE: There is not a real good way to store sensitive information on the
  // client, is there? This is an attempt at using tokens stored solely in RAM.
  // Alternatively session storage would be an option, but this could be read
  // out.

  // NOTE: http://tools.ietf.org/html/draft-ietf-oauth-v2-27#section-10.12
  // NOTE: https://developers.google.com/accounts/docs/OAuth2UserAgent

  /**
   * @object flux Maintain flux in memory.
   **/
  flux = {
    "active_record": {}
  };

  /* ====================================================================== */
  /*                               APP                                      */
  /* ====================================================================== */
  /*
   * Object containing application related methods
   */
  app = {};

  /**
   * Fallback for undefined arrays (faster to lookup than to create)
   */
  app.empty_array = [];

   /**
   * Timer for 500ms delayed actions
   */
  app.timer = 0;

  /**
   * Navigate to a new page after performing some action
   * @method navigate
   * @param {Object} obj The action object
   * @param {Object} response The action response
   */
  app.navigate = function (obj, response) {
    var answer, id, goto_page, decode;

    if (obj.state.callback) {
      app.util.loader("", "status_dict.forwarding");
      answer = util.parse(response);
      id = answer.id;
      decode = /%[0-9a-f]{2}/i.test(id);
      goto_page = decode ? id : window.encodeURIComponent(id);
      $.mobile.changePage(obj.state.callback.replace("__id__", goto_page));
    }
  };

  /**
   * Handler for search
   * @method search
   * @param {object} config Action Object
   */
  app.search = function (config) {

    // update gadget state
    config.state.query = storage.parseQuery(
      config.state.initial_query,
      config.state.type,
      null,
      config.element.value,
      config.state.query.select_list,
      config.state.restore
    );

    // update gadget
    app.content.set(
      {
        "portal_type_source": config.state.type,
        "portal_type_title": config.state.title,
        "property_dict": util.mergeObject(
          {"dynamic": true},
          config.state.dyno_dict
        ),
        "scheme": config.state.scheme
      },
      {
        "reference": config.id,
        "href": config.state.href,
        "fragment_list": config.state.fragment_list,
        "layout_level": config.state.layout_level
      },
      false
    )
      .fail(app.util.error);
  };

  /**
   * Sort a selection of elements
   * @method sort
   * @param {object} e Event triggering the sort action
   * @param {string} direction Direction to sort
   * @param {string} prev Previous icon
   * @param {string} next Next icon
   */
  // TODO: overwrite direction on single state sorting
  app.sort = function (config, direction, prev, next) {
    var i, in_array, sort_by, delay, state;

    // NOTE: if column title is not set, we might be sortling a list!
    sort_by = config.element.getAttribute("data-column-title");
    state = config.state;

    if (sort_by) {
      // sorting button update
      config.element.setAttribute("data-direction", direction);
      config.element.setAttribute("data-icon", next);
      config.element.className = config.element.className.replace(
        util.filterForClass("ui-icon-" + prev),
        " ui-icon-" + next
      );
      delay = 500;
    } else {
      delay = 0;
    }

    // trigger sort after 500ms delay, so user can pick sorting criteria
    util.clearTimer();

    // check if we are already sorting by this sort_by value
    for (i = 0; i < state.query.sort_on.length; i += 1) {
      if (state.query.sort_on[i][0] === sort_by) {
        in_array = true;
        break;
      }
    }

    // add sorting to query
    switch (direction) {
    case "desc_123":
    case "desc_abc":
      if (sort_by) {
        if (in_array === undefined) {
          state.query.sort_on.push([sort_by, "descending"]);
        } else {
          state.query.sort_on.splice(i, 1)
            .push([sort_by, "descending"]);
        }
      }
      break;
    case "asc_123":
    case "asc_abc":
      if (sort_by) {
        if (in_array === undefined) {
          state.query.sort_on.push([sort_by, "ascending"]);
        } else {
          state.query.sort_on.splice(i, 1)
            .push([sort_by, "ascending"]);
        }
      }
      break;
    default:
      // need to remove a column from sorting when set to undefined
      if (sort_by && in_array) {
        state.query.sort_on.splice(i, 1);
      }
      break;
    }

    // wait 500ms on multi-state sorting (table), single state trigger directly
    app.timer = window.setTimeout(function () {
      app.content.set(
        {},
        {
          "reference": config.id,
          "href": state.href,
          "fragment_list": state.fragment_list,
          "layout_level": state.layout_level
        },
        false
      )
        .fail(app.util.error);

      // reset timer
      app.timer = 0;
    }, delay);
  };

  /**
   * Highlight checkboxes and set a state on items selected
   * @method check
   * @param {object} config Action Object
   * @param {boolean} all Boolean determining whether to check all records
   */
  app.check = function (config, all) {
    var i,
      element_list,
      checks,
      check,
      checked_or_not,
      id,
      state,
      id_list;

    state = config.state;
    checked_or_not = config.element.checked;
    id = config.element.id.replace("select_", "");

    if (config && state.selected === undefined) {
      state.selected = [];
    }

    if (all !== undefined) {
      id_list = [];

      element_list = config.gadget
        .getElementsByTagName("TBODY")[0]
        .getElementsByTagName("TR");

      for (i = 0; i < element_list.length; i += 1) {
        checks = element_list[i].childNodes[0].getElementsByTagName("INPUT");

        if (checks.length > 0) {
          check = checks[0];
          if (check.type === "checkbox") {
            check.checked = checked_or_not;
            id_list.push(check.id);
            // TODO: need to JQM refresh...
            $(check).checkboxradio("refresh");
          }
        }
      }
      // override if indeterminate to select ALL (not only visible)
      if (config.element.indeterminate) {
        id_list = ["all"];
      }
    }

    if (checked_or_not === false) {
      if (all === undefined) {
        util.deleteFromArray(state.selected, id);
      } else {
        state.selected = [];
      }
    } else {
      if (all === undefined) {
        // radios only allow single selection
        if (config.element.type === "radio") {
          state.selected = [id];
        } else {
          state.selected.push(id);
        }
      } else {
        state.selected = id_list;
      }
    }

    // update gadget state
    config.gadget.state = {
      "fragment_list": state.fragment_list,
      "gadget": state.gadget,
      "query": state.query,
      "total": state.total,
      "method": state.method,
      "selected": state.selected
    };

    // update info fields
    // TODO: parentNode is not a proper selector, this should be done
    // by storing info fields associated with a gadget in state and
    // updating from there!
    app.setInfo(
      config.gadget.parentNode,
      state.query,
      state.total,
      state.selected
    );
  };

  /**
   * Handler for pagination
   * @method paginate
   * @param {object} config Action Object
   * @param {string} type Where to paginate to
   * @param {string} value New limit when changing number of records
   */
  app.paginate = function (config, type, value) {
    var start, records, total, state, current_limit;

    state = config.state;
    total = state.total;
    current_limit = state.query.limit || state.initial_query.limit;

    if (config.gadget) {
      if (state && current_limit) {
        switch (type) {
        case "first":
          start = 0;
          records = current_limit[1];
          break;
        case "next":
          start = current_limit[0] + current_limit[1];
          records = current_limit[1];
          break;
        case "prev":
          start = current_limit[0] - current_limit[1];
          records = current_limit[1];
          break;
        case "last":
          start = total - current_limit[1];
          records = current_limit[1];
          break;
        case "limit":
          start = current_limit[0];
          records = parseInt(value, null);
          break;
        }

        if ((start > total && total !== undefined) || start < 0) {
          return;
        }

        // set new limits
        state.query.limit = [start, records];

        // reset selected
        if (state.selected) {
          state.selected = [];
        }

        // update gadget
        app.content.set(
          {
            "portal_type_source": state.type,
            "portal_type_title": state.title,
            "property_dict": util.mergeObject(
              {"dynamic": true},
              state.dyno_dict
            ),
            "scheme": state.scheme
          },
          {
            "reference": config.id,
            "href": state.href,
            "fragment_list": state.fragment_list,
            "layout_level": state.layout_level
          },
          false
        )
          .fail(app.util.error);

      } else {
        app.util.error("No state/limit information stored for gadget");
      }
    } else {
      app.util.error("Action is missing reference gadget");
    }
  };

  /**
   * Update info fields (field displaying some sort of information
   * @method "
   * @param {object} element Element containing info fields
   * @param {object} options Query object used to get records
   * @param {integer} total Total Records
   * @param {integer} selected Total Records currently selected
   */
  // WARNING: complex_queries dependency, IE8 & QSA
  // TODO: refactor
  // TODO: don't reset all 4 fields on every action!
  app.setInfo = function (element, options, total, selected) {
    var i, j, l, selection, info_field, min, pointer, no_items,
      text_snippet, i18n_snippet, zero_no_show, select_counter, no_limit,
      slot, first, last, info, info_field_list, generateInfo, no_total,
      use_total, total_records;

    info_field_list = element.querySelectorAll(".info");
    info = "";
    no_total = total === undefined &&
      app.storage_dict.property_dict.skip_total_records;

    generateInfo = function (text, text_i18n) {
      return factory.element(
        "span",
        {"className": "translate"},
        {"data-i18n": "global_dict." + text_i18n},
        {"text": text}
      );
    };

    // skip_total
    if (no_total) {
      use_total = 0;
    } else {
      use_total = total;
    }

    // on updates, info_field_list may be empty, because only part of the
    // gadget is updated. Need to find info_fields by hand
    if (info_field_list.length === 0) {
      info_field_list = [];
      selection = document.querySelectorAll(".info");
      for (i = 0; i < selection.length; i += 1) {
        pointer = selection[i].getAttribute("data-reference");
        if (pointer === element.id) {
          info_field_list.push(selection[i]);
        }
      }
    }

    // loop info fields, set text and information
    for (j = 0; j < info_field_list.length; j += 1) {
      info_field = info_field_list[j];
      slot = undefined;

      switch (info_field.getAttribute("data-info")) {
      case "records":
        if (use_total !== undefined) {
          no_limit = options.limit[0] || 0;
          no_items = options.limit[1] || 0;
          zero_no_show = (no_limit === 0 && use_total !== 0) ? 1 : no_limit;
          if (no_total) {
            min = ((no_limit + no_items === 0) ? use_total :
                no_limit + no_items);
            total_records = "";
          } else {
            min = Math.min(
              use_total,
              ((no_limit + no_items === 0) ? use_total : no_limit + no_items)
            );
            total_records = "/" + use_total;
          }
          info = zero_no_show + "-" + min + total_records;
          text_snippet = " Records";
          i18n_snippet = "records";
          slot = 0;
        }
        break;
      case "sorted":
        if (options.sort_on.length > 0) {
          info = "";
          text_snippet = "Sorting: ";
          i18n_snippet = "sorted";
          slot = 1;
          for (l = 0; l < options.sort_on.length; l += 1) {
            if (l > 0) {
              info += " & ";
            }
            info += options.sort_on[l][0];
          }
        }
        break;
      case "selected":
        select_counter = 0;
        if (selected) {
          if (selected[0] === "all" && no_total === undefined) {
            select_counter = use_total;
          } else {
            select_counter = selected.length;
          }
        }
        info = select_counter;
        text_snippet = " item(s) selected";
        i18n_snippet = "selected";
        slot = 0;
        break;
      case "filter":
        if (options.last) {
          text_snippet = "Search: ";
          i18n_snippet = "search";
          slot = 1;
          info = options.last;
        }
        break;
      }

      // initial fill or update
      if (!slot) {
        if (info_field.children.length === 0) {
          first = document.createTextNode(info);
          last = generateInfo(text_snippet, i18n_snippet);
          info_field.appendChild(slot === 0 ? first : last);
          info_field.appendChild(slot === 0 ? last : first);
        } else {
          info_field.childNodes[slot].nodeValue = info;
        }
      } else {
        info_field.textContent = "";
      }
    }
  };

  /**
   * Action handler, routing actions to specified method
   * @method action
   * @param {object} e Event that triggered the action
   */
  // TODO: clean this up
  app.action = function (e) {
    var type, tag, val, action, handler, fallback;

    type = e.type;
    tag = e.target.tagName;

    if (type === "click" && e.target.getAttribute("data-rel") === null) {
      e.preventDefault();
      if (type === "click" && (tag === "SELECT" ||
        (tag === "INPUT" && e.target.type !== "submit"))) {
        return;
      }
    }
    if (type === "change" && tag === "SELECT") {
      val = e.target.options[e.target.selectedIndex].value;
    }
    // JQM bug on selects
    // TODO: remove once fixed
    if (tag === "SPAN" || tag === "OPTION") {
      return;
    }
    // map
    action = e.target.getAttribute("data-action");
    if (!action) {
      fallback = e.currentTarget.getAttribute("data-action");
    }
    handler = map.actions[fallback || action];

    if (fallback || action) {
      if (handler) {
        handler(app.parseAction(e), val);
      } else {
        app.util.error("Action: No method defined");
      }
    } else {
      app.util.error("Action: No action defined for element");
    }
  };

  /**
   * Generate action object for every action call
   * @method parseAction
   * @param {object} e Event triggering an action
   * @return {object} action object
   **/
  app.parseAction = function (e) {
    var element, id, current, response, has_form;

    if (e.type === "popupbeforeposition" || e.type === "panelopen") {
      return {
        "id": e.target.id,
        "gadget": e.target
      };
    }
    // NOTE: currentTarget needed for bubbling (eg tree)
    current = e.currentTarget === document ? undefined : e.currentTarget;
    element = current || e.target || e;

    // NOTE: used to be if no popup/panel, use data-ref or data-url else if
    // no href use data-url else href. Should work if element (button) does
    // not have a reference!
    id = element.getAttribute("data-reference") ||
        (element.href || "").split("#")[1] ||
            util.getPage().getAttribute("data-url");

    response = {
      "element": element,
      "id": id,
      "gadget": document.getElementById(id)
    };

    if (response.gadget === null) {
      return response;
    }

    has_form = response.gadget.getElementsByTagName("form");
    return util.mergeObject(
      {
        "form":  has_form ? has_form[0] : undefined,
        "state": (response.gadget || {}).state
      },
      response
    );
  };

  /**
   * Set the page title
   * @method setPageTitle
   * @param {string} page_i18n Lookup value for title
   * @param {object} page_header Header, in case it's still being generated
   */
  app.setPageTitle = function (page_i18n, page_header) {
    var title,
      value = map.actions.translateLookup(page_i18n),
      header = util.getHeader(page_header);

    if (util.testForString("ui-header", header.className)) {
      title = header.getElementsByTagName("h1")[0];
      if (title) {
        title.setAttribute("data-i18n", (page_i18n || ""));
        title.removeChild(title.childNodes[0]);
        title.appendChild(document.createTextNode((value || "\u00A0")));
      }
    }
    // also set document title
    document.title = value;
  };

  /**
   * Parse a page for gadgets and run page setup
   * @method parsePage
   * @param {object} e Event (pagebeforechange)
   * @param {object} data Data passed along with this (JQM) event
   */
  app.parsePage = function (e, data) {
    var page, base, config, raw_url, handle, clean_url, parsed_url, first,
      skipper, base_page;

    if (data) {

      // update page title on backwards transition to the first page (still
      // is in DOM, so no new page created, so no title update...)
      // TODO: not bulletproof i18n handling... we expect i18n to be defined
      // TODO: blocker is not nice
      if (typeof data.toPage === "object") {
        base_page = data.toPage[0];
        if ($.mobile.firstPage[0].getAttribute("data-url") ===
            base_page.getAttribute("data-url")) {
          app.setPageTitle("global_dict.home");

          // NOTE: this will allow going back, but only if we are not
          // coming from root
          if (!app.deeplink_flag) {
            skipper = true;
            app.deeplink_flag = true;
          }
        }
      }

      // determine url to load
      // TODO: leaning out of the window here!
      if (data.options.link) {
        raw_url = data.options.link[0].href;
      } else {
        raw_url = data.toPage;
      }

    } else {
      raw_url = window.location.href;
    }

    if (typeof raw_url === "string") {

      // decode
      if (data && data.options.reverse) {
        raw_url = window.decodeURIComponent(raw_url);
      }
      config = app.util.parseLink(raw_url);

      // NOTE: if we start from home we must prevent reloading home
      // because it will be kept in the DOM
      if (!config.deeplink) {
        // we start from home, so we must not allow going back
        // and reloading
        app.deeplink_flag = true;
      }

      if (e) {
        page = util.getPage(raw_url.split("#").pop());
        base = page ? page.getAttribute("data-external-page") : null;
        first = $.mobile.firstPage[0].getAttribute("data-url") ===
            config.data_url;

        if ((first || (page && base) || raw_url === $.mobile.getDocumentUrl() ||
            data.options.role === "popup") && !skipper) {
          // stop us, JQM can go
          return RSVP.resolve(undefined);
        }
        if ((util.getPage(config.data_url) && base !== null)) {
          e.preventDefault();
          // stop us and JQM
          return RSVP.resolve(undefined);
        }
        handle = true;
        // stop JQM, we go
        e.preventDefault();

      } else {
        // overwrite JQM history, so deeplinks are correctly handled
        if ($.mobile.navigate.history.initialDst &&
            window.location.hash !== "") {

          // this is the inital page (not) loaded
          // this page needs to be stored as initial page in JQM
          clean_url = window.location.href.split("#")[0];
          parsed_url = $.mobile.path.parseUrl(clean_url);

          // NOTE: remove initialDist, otherwise closing popups will trigger
          // double backward transition.
          delete $.mobile.navigate.history.initialDst;

        // NOTE: Correctly set index as first page in JQM history
          $.mobile.navigate.history.stack[0].hash = "";
          $.mobile.navigate.history.stack[0].url = clean_url;
          $.mobile.path.documentUrl = parsed_url;
          $.mobile.path.documentBase = parsed_url;
        }
      }
    }

    // fetch pageIndex and trigger loading of page and gadgets of a view
    if (e === undefined || handle) {
      return storage.fetchConfigAttachment({
        "storage": app.storage_dict.settings,
        "file": "pages",
        "attachment": config.root.split("?")[0],
        "pass": undefined
      })
        .then(function (reply) {
          // pick and generate page
          return app.content.set(
            reply.children[config.layout_level || 0],
            config,
            util.getPage(config.data_url) ? undefined : true
          );
        })
        .then(app.setPageBindings)
        .fail(app.util.error);
    }
    return RSVP.resolve(undefined);
  };

  /**
   * Set global bindings for all application elements
   * @method setGlobalBindings
   */
  //TODO: merge with pagebindings, so all jQuery is at one place!
  app.setGlobalBindings = function () {
    $(document)

      .enhanceWithin()

      .on("click", "img.ui-title-logo", function () {
        $.mobile.changePage("#home");
      })

      // generate dynamic pages
      .on("pagebeforechange", function (e, data) {
        app.parsePage(e, data)
          .fail(app.util.error);
      })

      // clean dynamic pages on hide
      // NOTE: or hack JQM and call "bindRemove" on dynamic pages, too
      .on("pagehide", "div.ui-page", function () {
        if (this.getAttribute("data-external-page")) {
          $(this).page("destroy").remove();
        }
      })

      // block form submits
      .on("submit", "form", function (e) {
        e.preventDefault();
        return false;
      })


      // panel close (since default does not work on dynamic content
      // TODO: integrate this into action if there is no other way!
      .on("click", "div.ui-panel a.ui-panel-close", function () {
        $(this).closest("div.ui-panel").panel("close");
      })

      // global actions
      .on("click change keyup input", ".action", function (e) {
        var val, last, element, type;
        element = e.target;
        type = element.type;

        // delay all input field actions allowing user to type/select
        if (element.tagName === "INPUT") {
          switch (type) {
          case "button":
          case "checkbox":
          case "reset":
            app.action(e);
            break;
          default:
            val = element.value;
            last = element.getAttribute("data-last");
            if ((last && last === val) && type !== "submit") {
              return;
            }
            util.clearTimer();

            // give user half second to pick his state
            app.timer = window.setTimeout(function () {
              element.setAttribute("data-last", val);
              app.action(e);
              app.timer = 0;
            }, 500);
            break;
          }
        } else {
          app.action(e);
        }
      })

      // popup content loading
      .find(".ui-popup, .ui-panel")
        .on("popupbeforeposition panelopen", function (e) {
        factory.util.generateDynamicContents(app.parseAction(e));
      });

      // remove focus from active elements on key-tab switches
    window.onblur = function () {
      document.activeElement.blur();
    };

  };

  /**
   * Set bindings on page specific elements after content has been appended
   * @method setPageBindings
   */
  // TODO: add local popups!
  app.setPageBindings = function () {
    var i, j, form_element, filterable, captcha, form_list, filter_list;

    form_list = document.getElementsByTagName("form");
    filter_list = document.querySelectorAll("[data-filter]");

    // disable default filtering of JQM filterable
    for (i = 0; i < filter_list.length; i += 1) {
      filterable = filter_list[i];

      if (filterable.getAttribute("data-bound") === null) {
        filterable.setAttribute("data-bound", true);
        $(filterable).on("filterablebeforefilter", function (e) {
          e.preventDefault();
        });
      }
    }

    // add validation to all forms
    for (j = 0; j < form_list.length; j += 1) {
      form_element = form_list[j];
      captcha = document.getElementById(form_element.id + "_captcha");

      // captcha
      if (captcha) {
        app.init.declare(
          "http://www.google.com/recaptcha/api/js/recaptcha_ajax.js",
          "js"
        ).then(function () {
          Recaptcha.create(
            captcha.getAttribute("data-key"),
            captcha.id,
            {"theme": "red", "callback": Recaptcha.focus_response_field}
          );
        }).fail(app.util.error);
      }

      if (form_element.getAttribute("data-bound") === null) {
        form_element.setAttribute("data-bound", true);

        // NOTE: the script is mapped to validval, so replacing it
        // requires it to add a different plugin here as well as
        // updating all data-vv fields being set in mapFormField() and
        // generateFormElement

        $(form_element).validVal({
          "validate": {
            "onKeyup": "valid",
            "onBlu": "valid"
          },
          "fields": {
            "hidden": true
          },
          //customValidations: util.declareJS(),
          "form": {
            "onInvalid": function () {
              util.return_out();
            }
          }
        });
      } else {
        // TODO: AAAARGH! so much jquery...
        // TODO: AND IT DOES NOT WORK... Â§$%&/()=!
        $(form_element).trigger("addField", $(form_element).find(".required"));
      }
    }
  };

  /* ====================================================================== */
  /*                                UTILS                                   */
  /* ====================================================================== */

  /**
   * Object for all generic utility methods
   */
  util = {};

  /**
   * Inherit objects to an array of items
   * @inherit
   * @param {array} kids Array of elements to inherit objects
   * @param {object} Objects to inherit
   * @return {array} kids
   **/
  util.inherit = function (kids, property_dict) {
    var i, kids_len, obj;

    for (i = 0, kids_len = kids.length; i < kids_len; i += 1) {
      for (obj in property_dict) {
        if (property_dict.hasOwnProperty(obj)) {
          kids[i][obj] = property_dict[obj];
        }
      }
    }
    return kids;
  };

  /**
   * Crop a string (data field)
   * @method crop
   * @param {string} str String to crop
   * @param {string} crop String to crop
   * @return {string} cropped string
   **/
  util.crop = function (str, crop) {
    var re = new RegExp(crop, 'g');
    return str.replace(re, '');
  };

  /**
   * format a string (data field)
   * @method format
   * @param {string} str String to format
   * @param {object} spec Formating options (type, etc)
   * @return {string} formatted string
   **/
  util.format = function (str, spec) {
    switch (spec.type) {
    case "integer":
      if (!isNaN(parseFloat(str)) && isFinite(str)) {
        return Math.round(str).toFixed(spec.digits);
      }
      return str;
    }
    return str;
  };

  /**
   * generates a text based on mapping criteria
   * @method generateText
   * @param {string} str String to modify
   * @param {object} spec Formatting options
   * @param {object} record Full record (only used for merge)
   * @return {string} modified string
   **/
  util.generateText = function (str, spec, record) {
    if (spec.crop) {
      str = util.crop(str, spec.crop);
    }
    if (spec.format) {
      str = util.format(str, spec.format);
    }
    // TODO: ? vs merge -> make generic
    if (spec.mergeText) {
      str += " " + record[spec.mergeText];
    }
    return str;
  };

  /**
   * Fetch a value from an object based on an array path
   * @method fetchByArray
   * @param {object} obj Object containing a value
   * @param {array} arr Path leading to a value
   * @return {string} value
   **/
  util.fetchByArray = function (obj, arr) {
    var n, key;

    for (n = 0; n < arr.length; n += 1) {
      key = obj[arr[n]] || key[arr[n]];
    }
    return key;
  };

  /**
   * Generate a UUID ticket for a transaction
   * @method uuid
   * @return {string} UUID
   **/
  util.uuid = function () {
    function S4() {
      return ('0000' + Math.floor(
        Math.random() * 0x10000 /* 65536 */
      ).toString(16)).slice(-4);
    }
    return S4() + S4() + "-" +
      S4() + "-" +
      S4() + "-" +
      S4() + "-" +
      S4() + S4() + S4();
  };

  /**
   * Generate a random identifier
   * @method generateRandomIdentifier
   * @param {string} len Length of identifier
   * @return {String} Identifier.
   **/
  util.generateRandomIdentifier = function (len) {
    //return Math.random().toString(36).slice(2);
    var s = '',
      randomchar = function () {
        var n = Math.floor(Math.random() * 62);
        if (n < 10) {
          return n; //1-10
        }
        if (n < 36) {
          return String.fromCharCode(n + 55); //A-Z
        }
        return String.fromCharCode(n + 61); //a-z
      };

    while (s.length < len) {
      s += randomchar();
    }
    return s;
  };

  /**
   * Clear timer on retriggering methods
   * @clearTimer
   **/
  util.clearTimer = function () {
    if (app.timer) {
      window.clearTimeout(app.timer);
      app.timer = 0;
    }
  };

  /**
   * Retrieve the global or active page header
   * @method getHeader
   * @param {array} page_header Info on page specific header
   * @return {object} HTML element
   **/
  util.getHeader = function (page_header) {
    return (page_header && page_header.length) ? page_header[0] :
        (document.getElementById("global-header") ||
            // WARNING: IE8- children() retrieves comments, too
            util.getPage().children[0]);
  };

  /**
   * Clone an object
   * @method cloneObject
   * @param {object} object Object to be cloned
   * @return {object} clone
   **/
  util.cloneObject = function (obj) {
    var i, clone = {};
    for (i in obj) {
      if (obj.hasOwnProperty(i)) {
        clone[i] = obj[i];
      }
    }
    return clone;
  };

  /** Determine if a property exists in an object
   * @method findKey
   * @param {object} Object The object to search
   * @param {string} Pointer The nested object pointer
   * @param {string} Key The key under which it is stored
   * @param {string} Key The value to search
   * @return {boolean} true/null
   */
  // TODO: Storage specific method, move
  util.findKey = function (object, pointer, key, value) {
    var i, obj;

    // simple query
    if (object[key] === value) {
      return true;
    }
    if (object[pointer]) {
      for (i = 0; i < object[pointer].length; i += 1) {
        obj = object[pointer][i];
        if (obj[key] === value) {
          return true;
        }
        if (obj[pointer]) {
          util.findKey(obj[pointer], pointer, key, value);
        }
      }
    }
    return null;
  };

  /**
   * Promise-optimized Ajax (same as used in JIO, thx
   * http://git.erp5.org/gitweb/jio.git/blob_plain/refs/heads/master:/jio.js
   * @method ajax
   * @param  {String} [param.type="GET"] The request method
   * @param  {String} [param.dataType=""] The data type to retrieve
   * @param  {String} param.url The url
   * @param  {Any} [param.data] The data to send
   * @param  {Function} [param.beforeSend] A function called just before send
   *   request. The first parameter of this function is the XHR object.
   * @return {Promise} The promise
   */
  // NOTE: specifying dataType will not work cross-browser as CHROME/IOS
  // falsely parse the returned JSON string into a "broken" object
  // parsing needs to be done here using
  util.ajax = function (param) {
    var xhr = new XMLHttpRequest();
    return new RSVP.Promise(function (resolve, reject, notify) {
      var k;
      xhr.open(param.type || "GET", param.url, true);
      if (typeof param.headers === 'object' && param.headers !== null) {
        for (k in param.headers) {
          if (param.headers.hasOwnProperty(k)) {
            xhr.setRequestHeader(k, param.headers[k]);
          }
        }
      }
      xhr.addEventListener("load", function (e) {
        if (e.target.status >= 400) {
          return reject(e);
        }
        resolve(e);
      });
      xhr.addEventListener("error", reject);
      xhr.addEventListener("progress", notify);
      if (typeof param.beforeSend === 'function') {
        param.beforeSend(xhr);
      }
      xhr.send(param.data);
    }, function () {
      xhr.abort();
    });
  };

  /**
   * Get the active JQM page in JavaScript-only
   * @method getPage
   * @param {string} url url of the page to fetch
   * @return {string} id of active page
   */
  util.getPage = function (url) {
    var i, kid, len, kids = document.body.children;

    // reverse, because in JQM last page is the active page!
    for (len = kids.length, i = len - 1; i >= 0; i -= 1) {
      kid = kids[i];

      if (util.testForString("ui-page", kid.className)) {
        if (url === undefined || kid.getAttribute("data-url") === url) {
          return kid;
        }
      }
    }
    return undefined;
  };

  /**
   * Remove all children of an element
   * @method deleteChildren
   * @param {object} el Element to remove children from
   */
  util.deleteChildren = function (el) {
    while (el.hasChildNodes()) {
      el.removeChild(el.lastChild);
    }
  };

  /**
   * Reverse an array
   * @method reverseArray
   * @param {array} array Array to reverse
   * @returns {array} keys Reversed array
   */
  util.reverseArray = function (array) {
    var k, keys = [];

    for (k = 0; k < array.length; k += 1) {
      keys.unshift(array[k]);
    }
    return keys;
  };

  /**
   * Delete an element from an array
   * @method deleteFromArray
   * @param {array} array Original array
   * @param {string} element Element to remove
   * @return {array} new array
   */
  util.deleteFromArray = function (array, element) {
    var i;

    for (i = 0; i <= array.length; i += 1) {
      if (array[i] === element) {
        return array.splice(i, 1);
      }
    }
    return array;
  };

  /**
   * Convert 1,2,3 into a,b,c
   * @method: toLetters
   * @param: {number} number Number to convert
   * @return: {string} letter
   */
  util.toLetter = function (number) {
    return String.fromCharCode(97 + ((number - 1) % 26));
  };


  /**
   * Filter a string for a class name
   * @method filterForClass
   * @param  {string} class The string to filter
   * @return {object} regex
   */
  util.filterForClass = function (className) {
    return new RegExp("(?:^|\\s)" + className + "(?!\\S)", "g");
  };

  /**
   * Test for a class name
   * @method testForString
   * @param {string} search_string The string to test
   * @param {string} full_string The string to test against
   * @param {boolean} nowrap Whether to wrap in space
   * @return {boolean} result True/False
   */
  util.testForString = function (search_string, full_string, nowrap) {
    var s = nowrap ? "" : " ";
    return (s + full_string + s).indexOf(s + search_string + s) > -1;
  };

  /*
   * Return
   * @method return
   */
  // TODO: remove only used for JSLint for validval
  util.return_out = function () {
    return;
  };

  /**
   * parse to JSON if element is not in JSON
   * @method parse
   * @param {object/string} data Data to parse
   * @return {object} object Parsed object
   */
  util.parse = function (data) {
    if (typeof data === "string") {
      return JSON.parse(data);
    }
    return data;
  };

  /**
   * Merge object 1 and object 2
   * @method merge
   * @param {object} Object 1 to merge into object 2 (including overwrites)
   * @param {objcet} Object 2
   * @return {object} merged object
   **/
  util.mergeObject = function (a, b) {
    var attribute;

    for (attribute in a) {
      if (a.hasOwnProperty(attribute)) {
        b[attribute] = a[attribute];
      }
    }
    return b;
  };

  /* ********************************************************************** */
  /*                            App Content                                 */
  /* ********************************************************************** */
  app.content = {};

  /**
   * Set static and dynamic elements for generation
   * @method set
   * @param {object} content_dict Contains content related parameters
   * @param {object} url_dict Contains navigation related parameters
   * @param {boolean} create Flag to create or refresh
   * @return {object} promise
   **/
  // TODO: change this to widget, state and status
  app.content.set = function (content_dict, url_dict, create) {
    var pass, is_html, is_href;

    // pack
    pass = {
      "content_dict": content_dict || {},
      "url_dict": url_dict || {},
      "data_dict": {},
      "create": create
    };

    is_href = pass.url_dict.href;

    // waste of performance
    if (is_href) {
      is_html = is_href.split(".").length > 1 &&
          is_href.split(".")[1] === "html";
    }

    // flag static content and skip loading config
    pass.skip = !pass.url_dict.href && !pass.content_dict.portal_type_source;
    pass.no_config = pass.skip || !!pass.content_dict.portal_type_source;

    // HACK Thierryfic hack
    if (is_html) {
      return util.ajax({
        "url": "data/" + pass.url_dict.href
      }).then(function (e) {
        // Don't look...
        var i, len, fragment, response = $.parseHTML(
          e.target.response.replace("/erp5/", "/", "g")
        );

        fragment = document.createDocumentFragment();
        for (i = 0, len = response.length; i < len; i += 1) {
          fragment.appendChild(response[i]);
        }

        return fragment;
      });
    }

    // and go
    return storage.fetchConfigAttachment({
      "storage": app.storage_dict.settings,
      "file": (pass.url_dict.href ?
          app.storage_dict.property_dict.name_dict.gadgets :
              app.storage_dict.property_dict.name_dict.settings),
      "attachment": pass.url_dict.href ||
          app.storage_dict.property_dict.name_dict.configuration,
      "pass": pass
    })
      .then(app.content.fields)
      .then(app.content.test)
      .then(app.content.sample)
      .then(app.content.total)
      .then(app.content.query)
      .then(app.content.make)
      .fail(app.util.error);

  };

  /**
   * Load field definitions if necessary
   * @method fields
   * @param {object} Pass through object and reply (authentication info)
   * @return {object} field defintions and pass through
   */
  app.content.fields = function (reply) {
    var pass = reply.pass, query, crop;

    if (!pass.skip) {

      // (dynamic element) config_dict
      // NOTE: so config_dict will always be present and we don't need to
      // ever test for it again after here.... because if we fetch by URL
      // whatever was fetched is here and if we just render it will be
      // on content_dict.
      pass.config_dict = util.parse(reply.response) || pass.content_dict;
      delete pass.no_config;

      // test for auth based access
      pass.grant = true;

      // update initial query with urlQuery if one was passed
      if (pass.url_dict.url_query) {
        crop = pass.config_dict.property_dict.url_crop;
        query = util.mergeObject(
          pass.url_dict.url_query,
          pass.config_dict.initial_query || {}
        );

        // TODO: TOTALLY UGLY hack to allow 2 gadgets who require different urls
        // to share... remove ASAP
        if (crop) {
          query.query = query.query.replace(crop, "");
        }
        pass.config_dict.initial_query = query;
      }

      // fetch field definitions - why make a query without fieldlist?
      if (pass.grant || pass.mode === "new") {
        app.util.loader("", "status_dict.loading_config");
        return storage.fetchConfigAttachment({
          "storage": app.storage_dict.settings,
          "file": app.storage_dict.property_dict.name_dict.data_type,
          "attachment": pass.config_dict.portal_type_title + "_fieldlist",
          "pass": pass
        });
      }
    }
    return {
      "pass": pass
    };
  };

  // ========================= SAMPLE DATA HANDLER ==========================
  /**
   * Test storage for data
   * @method test
   * @param {object} reply Response from previous promise and pass-params
   * @return {object} response object/promise
   */
  app.content.test = function (reply) {
    var pass = reply.pass;

    if (!pass.skip) {
      pass.data_dict.field_dict = util.parse(reply.response);

      // try to get 1 record for this portal_type
      if (app.storage_dict.property_dict.allow_sample_data && pass.grant &&
          (pass.create !== false) && pass.config_dict.initial_query) {
        return storage.fetch({
          "pass": pass,
          "storage": "items",
          "query": storage.parseQuery(
            {"limit": [0, 1]},
            pass.config_dict.portal_type_source
          )
        });
      }
    }
    return {
      "pass": pass
    };
  };


  /**
   * If no data is found in JIO, we need to load fake data for now
   * @method sample
   * @param {object} reply Response from previous promise and pass-params
   * @return {object} response object/promise
   */
  app.content.sample = function (reply) {
    var pass = reply.pass;

    // only when testing, so skip would not be undefined!
    if (reply.response) {
      if (util.parse(reply.response).data.total_rows === 0) {
        return util.ajax(
          {"url": "data/" + pass.config_dict.portal_type_title + "_sample.json"}
        )
        // STORE SAMPLE DATA HERE!
          .then(function (e) {
            var response, i, len, promise_list, record;

            response = util.parse(e.target.responseText);
            len = response.length;
            promise_list = [];

            // NOTE: no more need to test for and fetch fields, because they
            // will be defined, as this method is only called inside the
            // content-loop.
            // NOTE: do we still validate against field_dict?
            for (i = 0; i < len; i += 1) {
              record = response[i];
              record.portal_type = pass.config_dict.portal_type_source;

              promise_list[i] = storage.write({
                "sample_data": record,
                "sample_store": true,
                "state": {}
              });
            }

            return RSVP.all(promise_list)
              .then(function (response_list) {
                if (!response_list[0] && promise_list.length !== 0) {
                  app.util.loader("", "status_dict.internal_error", "ban");
                }
                // just return pass and continue
                return {
                  "pass": pass
                };
              })
              .fail(app.util.error);
          })
          .fail(app.util.error);
      }
    }
    return {
      "pass": pass
    };
  };

  // ========================================================================


  /**
   * Fetch total number of records
   * @method total
   * @param {object} Pass through object and reply (nothing new)
   * @return {object} response object/promise
   **/
  // TODO: integrate views properly into pageIndex!
  // TODO: no docById, get element from state itself!
  // TODO: find better way to determine "value"
  app.content.total = function (reply) {
    var dict, lib, pass = reply.pass, last;

    if (!pass.skip) {
      //pass.data_dict.field_dict = util.parse(reply.response);

      // update state object
      if (pass.create === false) {
        pass.state = document.getElementById(pass.url_dict.reference).state;
        pass.store_limit = pass.state.query.limit;
        pass.state.query.limit = [];

      // create state object
      } else {
        dict = pass.config_dict;
        lib = pass.url_dict;
        pass.state = {
          "dyno_dict": dict.property_dict,
          "scheme": dict.scheme,
          "type": dict.portal_type_source,
          "title": dict.portal_type_title,
          "fragment_list": lib.fragment_list,
          "layout_level": lib.layout_level,
          "href": lib.href,
          "mode": lib.mode,
          "view": dict.view,
          // HACK so it's available on storage ops
          "url_pointer": dict.property_dict.url_pointer
        };

        // TODO: determine value to query based on URL (this stinks...)
        if (lib.layout_level > 0) {
          last = lib.fragment_list.slice(-1)[0];

          if (!lib.mode || last !== lib.mode) {
            pass.data_dict.value =
                lib.fragment_list[lib.layout_level];
          }
        }

        // generate query object
        if (dict.initial_query) {
          pass.state.initial_query = dict.initial_query;
          pass.state.query = storage.parseQuery(
            dict.initial_query,
            dict.portal_type_source,
            dict.property_dict.initial_query_url_identifier,
            pass.data_dict.value
          );
        } else {
          pass.state.query = {};
        }
      }

      // TODO: get ALL RECORDS matching query (and make this if disappear ...)
      if ((!app.storage_dict.property_dict.skip_total_records &&
          pass.config_dict.skip_total_records !== true) &&
            ((pass.grant || pass.url_dict.mode === "new") &&
              (pass.state && pass.state.query._id === undefined) &&
                (pass.config_dict.initial_query || pass.create === false) &&
                  !pass.config_dict.property_dict.force_new)
          ) {
        // reset limit from sampling [0,1] and store query
        pass.state.query.limit = [];
        app.util.loader("", "status_dict.loading_total");
        return storage.fetch({
          "pass": pass,
          "storage": "items",
          "query": pass.state.query
        });
      }
    }
    return {
      "pass": pass
    };
  };

  /**
   * Fetch records matching query
   * @method query
   * @param {object} Pass through object and reply (total records)
   * @return {object} response object/promise
   **/
  app.content.query = function (reply) {
    var pass = reply.pass;

    if (!pass.skip) {
      // set total rows
      if (reply.response) {
        pass.state.total = util.parse(reply.response).data.total_rows;
      }

      // fetch full documents (unless force_field_definitions is set)
      if (pass.create === false) {
        if (!storage || (storage &&
          !app.storage_dict.property_dict.force_field_definitions)) {
          pass.state.query.include_docs = true;
        }
        pass.state.query.limit = pass.store_limit;

      // only fetch columns based on gadget configuration
      } else {
        if (pass.data_dict.field_dict &&
              storage &&
                app.storage_dict.property_dict.force_field_definitions &&
                  pass.config_dict.scheme
            ) {
          pass.data_dict.field_list = storage.makeSelectList(
            pass.config_dict.scheme
          );
        }

        pass.state.query = storage.parseQuery(
          pass.config_dict.initial_query,
          pass.config_dict.portal_type_source,
          pass.config_dict.property_dict.initial_query_url_identifier,
          pass.data_dict.value,
          pass.data_dict.field_list
        );
      }

      // get items/item
      if (pass.grant && !pass.config_dict.property_dict.force_new &&
          (pass.url_dict.mode !== "new" || !pass.config_dict.initial_query)
          ) {
        app.util.loader("", "status_dict.loading_set");
        return storage.fetch({
          "storage": "items",
          "query": pass.state.query,
          "pass": pass
        });
      }
    }
    return {
      "pass": pass
    };
  };

  /**
   * Generate static and dynamic HTML content, insert and enhance
   * @method make
   * @param {object} Pass through object and reply (queried records)
   * @return {object} response object/promise
   **/
  // TODO: refactor... appocalyptic
  // TODO: determine what should be inherit and make inherit object, that
  // will be passed down the rendering tree
  app.content.make = function (reply) {
    var pass, method, type, kids, promise_list, route,
      search, search_id, kid, is_html, is_dynamic, is_id, i, last,
      encoded, wrapper, active, selector, update_target, widget,
      quirk_dict, generator, pointer_results, data_total_rows, is_parameter,
      no_item, dyno, container, has_props, widget_dyno,
      set_reference;

    pass = reply.pass;
    pass.config_dict = pass.config_dict || {};
    type = pass.content_dict.type;
    method = factory.widget[type] || undefined;
    promise_list = [];

    // NOTE:
    // set results here, so kids.length is available in wrapper generation.
    // Also, if dynamic and no response (eg for "new" record, we set an empty
    // placeholder here which will generate an empty form
    if (!pass.skip) {
      pointer_results = util.parse(reply.response) ||
        {"data": {"total_rows": 0, "rows": []}};
      data_total_rows = pointer_results.data.total_rows;
    }

    // kids (must be declared before wrapper, because some wrapper need length
    if (pass.content_dict.view_dict) {
      kids = pass.content_dict.view_dict[pass.url_dict.mode] ||
        pass.content_dict.view_dict["default"];
    } else {
      kids = pass.content_dict.children || pass.config_dict.children || [];
    }

    // set generator
    if (pass.skip) {
      if (!pass.content_dict.generate) {
        route = type;
        // force custom elements into form layout...
        // NOTE: only used in service_instance_status to make a link go
        // through formElement, not sure this must be a viable option,
        // maybe better with custom, question is were do wrapper, label
        // come from.
        if (pass.content_dict.logic && pass.content_dict.logic.route) {
          route = pass.content_dict.logic.route;
        }

        switch (route) {
        case "input":
        case "select":
        case "textarea":
        case "force_form_element":
          generator = {
            "fragment": factory.widget.formElement(pass.content_dict)
          };
          break;
        case "divider":
        case "item":
          generator = {"fragment": pass.content_dict.content};
          break;
        case undefined:
          generator = {"fragment": document.createDocumentFragment()};
          break;
        default:
          generator = {"fragment": factory.element(pass.content_dict)};
          break;
        }
      }
    }

    // wrapper object and properties
    quirk_dict = util.mergeObject(
      (pass[method ? "content_dict" : "config_dict"].property_dict),
      {
        "create": pass.create,
        "update": pass.create === false ? true : null,
        "total_rows": data_total_rows,
        "length": kids.length,
        "layout_level": pass.url_dict.layout_level,
        "fragment_list": pass.url_dict.fragment_list,
        "data_url": pass.url_dict.data_url,
        "scheme": pass.config_dict.scheme,
        "field_dict": (pass.data_dict || {}).field_dict,
        "skip": pass.skip
      }
    );

    if (!generator) {
      if (method) {
        generator = method(quirk_dict);
      } else {
        generator = {"fragment": document.createDocumentFragment()};
      }
    }

    // switch back
    wrapper = generator;

    // make properties available to children in need
    wrapper.property_dict = quirk_dict;

    // no records returned, no_show set below on dyno
    // TODO: find better way to set dynamic flag
    if (quirk_dict.no_show) {
      no_item = factory.util.noItems(
        quirk_dict.no_show,
        wrapper.base,
        wrapper.count ? wrapper.count(quirk_dict.scheme[0]) : null
      );
      kids = util.inherit(
        no_item,
        {"scheme": quirk_dict.scheme, "field_dict": quirk_dict.field_dict}
      );
    }

    // dynamic content
    // TODO: 10 if-else...
    if (!pass.skip) {

      // =====================================================================
      // NOTE: store state on wrapper property_dict to inherit!
      wrapper.property_dict.state = pass.state;
      // =====================================================================

      // inherit item id, so it's available in form as indentifier
      if (pointer_results.data.total_rows === 1) {
        quirk_dict.data = {"_id": pointer_results.data.rows[0].doc._id};
      }
      // no items, need no show! (default to empty {} if none provided
      if (pointer_results.data.total_rows === 0) {
        if (quirk_dict.allow_new || quirk_dict.force_new) {

          // NOTE: on updates with force_new, reference must be passed to not
          // have undefined form-id values
          if (quirk_dict.update) {
            set_reference = {
              "doc": {},
              "reference": pass.url_dict.reference
            };
          }
          pointer_results = {
            "data": {
              "total_rows": 1,
              "rows": [set_reference || {"doc": {}}]
            }
          };
        } else {
          quirk_dict.no_show = quirk_dict.no_items || {};
        }
      }

      // set up fragment and child_selector
      if (quirk_dict.update !== true) {
        if (!quirk_dict.no_content) {
          // set reference for children in need
          quirk_dict.reference = "dyno_" + util.uuid();
          dyno = factory.element({
            "type": "div",
            "direct": {
              "id": quirk_dict.reference,
              "className": "dyno"
            }
          });
          // setup fragment and child_selector
          if (quirk_dict.wrap_gadget) {
            container = factory.element({
              "type": "div",
              "direct": {
                "className": "span_" + quirk_dict.wrap_gadget
              }
            });
            container.appendChild(dyno);
          }

          wrapper.fragment.appendChild(container || dyno);
          wrapper.child_selector = wrapper.fragment.querySelector(
            "#" + quirk_dict.reference
          );

          // add auto pagination bar
          if (quirk_dict.pagination) {
            kids.push(factory.util.paginationBar(quirk_dict.pagination));
          }

          // add caption
          if (quirk_dict.caption) {
            kids.push({
              "generate": "widget",
              "type": "controlbar",
              "property_dict": {"slot": quirk_dict.caption.slot || 1},
              "children": [{
                "type": "p",
                "direct": {"className": "ui-table-caption translate"},
                "attributes": {
                  "data-i18n": quirk_dict.caption.text_i18n || null
                },
                "logic": {"text": quirk_dict.caption.text}
              }]
            });
          }

          // add auto search bar
          if (quirk_dict.search) {
            search = factory.util.searchBar(quirk_dict.search);
            search_id = search.id;
            kids.unshift(search.config);
          }

          // add popup (local/global)
          switch (quirk_dict.need_popup) {
//         case "local":
//         if (util.getPage().querySelectorAll("div.local_popup") === null) {
//           target.appendChild(factory.widget.popup({}));
//         }
//         break;
          case "global":
            if (document.getElementById("global_popup") === null) {
              document.documentElement.appendChild(factory.widget.popup({}));
            }
            break;
          }
        }
      } else {
        // TODO: can this be done without storing on and querying DOM???!!!
        // TODO: THIS MAKES ME INSANE... qsa || widget does not work!
        widget = pass.state.gadget;
        widget_dyno = widget.querySelector("[data-update]") ||
            widget.getElementsByTagName("form")[0];
        wrapper.property_dict.map_children =
            widget_dyno.getAttribute("data-map");

        wrapper = util.mergeObject(
          factory.widget[widget_dyno.getAttribute("data-widget")](
            wrapper.property_dict
          ),
          wrapper
        );

        // add scheme and field_dict to list
        kids = util.inherit(
          pointer_results.data.rows,
          {"scheme": quirk_dict.scheme, "field_dict": quirk_dict.field_dict}
        );
      }
    }

    // loop over children
    if (kids.length) {

      // reset in wrapper, otherwise might be overwritten
      quirk_dict.length = kids.length;
      is_id = (quirk_dict.data || {})._id || quirk_dict.item_identifier;

      for (i = 0; i < kids.length; i += 1) {
        kid = kids[i];

        // TODO: if dynamic is properly inherited from parent to child
        // we don't need is_dynamic!
        is_dynamic = kid.property_dict && kid.property_dict.dynamic;
        is_html = kid.direct;

        // inherit item id to child elements (see above)
        if (is_id) {
          if (is_html && is_html.href) {
            kid.direct.href = kid.direct.href.replace("__id__", is_id);
          } else if (kid.property_dict) {
            // continue inheriting to all widgets
            kid.property_dict.item_identifier =
              window.encodeURIComponent(is_id);
          }
        }

        if (is_dynamic) {
          // NOTE: set, so children can inherit item id to update link templates
          // add attributes of injeceted elements (search bar)
          if (search_id) {
            kid.property_dict.filter = true;
            kid.property_dict.input = "#" + search_id;
          }

          // map on kid directly and inherit data down (only single items)
          if (quirk_dict.direct_map) {
            kid.property_dict.data = pointer_results.data.rows[0];

          // NOTE: we may be setting on a wrapper!
          } else {
            kid.property_dict = util.mergeObject(kid.property_dict, quirk_dict);
            kid.children = util.inherit(
              pointer_results.data.rows.concat(kid.children || []),
              {"scheme": quirk_dict.scheme, "field_dict": quirk_dict.field_dict}
            );
          }
        }

        // ===================================================================
        is_parameter = (kid.logic || {}).setParam;
        has_props = kid.property_dict;

        // inherit dynamic state to all children with property_dict
        if (has_props && wrapper.property_dict.state) {
          kid.property_dict.state = wrapper.property_dict.state;
        }

        // set dynamic Param(s)
        if (is_parameter) {
          kid = app.util.setParam(kid, wrapper);
        }
        // ===================================================================



        if (quirk_dict.update !== true || is_dynamic || quirk_dict.dynamic) {
          // for content loaded via href, generate URL dict and undefine kid
          // TODO: make sure this works and does not inherit to regular widgets
          if (wrapper.is_page && kid.href) {
            wrapper.inherit = {
              "href": kid.href,
              "url_query": pass.url_dict.url_query,
              "fragment_list": pass.url_dict.fragment_list,
              "layout_level": pass.url_dict.layout_level,
              "inherit": true,
              "mode": pass.url_dict.mode
            };
            kid = undefined;
          }

          if (quirk_dict.reference) {
            if (!!kid.attributes) {
              kid.attributes["data-reference"] = quirk_dict.reference;
            } else if (!!kid.property_dict) {
              kid.property_dict.reference = quirk_dict.reference;
            } else {
              kid.reference = quirk_dict.reference;
            }
          }

          // HACK for Thierry to pass reference into custom fields
          if (wrapper.reference) {
            kid.reference = wrapper.reference;
          }

          // set classes
          if (is_html) {
            kid.direct.className = (kid.direct.className || "") +
                factory.util.classes(kid) +
                    (i === 0 ? " ui-first-child " : "") +
                        (i === (kids.length - 1) ? " ui-last-child " : "");
          }

          // go-go-gadget-o-promise...
          promise_list[i] = map.element(kid, wrapper, i)
            .then(function (mapping) {
              if (mapping.inherit) {
                return app.content.set({}, mapping);
              }
              return app.content.set(mapping);
            })
            .fail(util.error);
        }
      }
    }

    return RSVP.all(promise_list)
      .then(function (response_list) {
        var h, l, m, len, done_target, wrapper_selector, response, content,
          last_child, detach, child_list;

        for (h = 0, l = response_list.length; h < l; h += 1) {
          response = response_list[h];
          done_target = undefined;

          if (response) {
            // generate target for and append response
            // NOTE: wrapper.spec is used for header only.
            // TODO: remove, spec must be available here, too
            // NOTE: len is needed for header only to place title
            // TODO: no longer needed, remove
            if (wrapper.target) {
              wrapper_selector = wrapper.target({
                "j": wrapper.child_selector.children.length,
                "len": kids.length,
                "config": wrapper.spec
              });

              // NOTE: wrapper_selector will return a fragment (set to
              // first/last-ElementChild or a DOM node
              // NOTE: Opera/old IOS do not support first/lastELEMENTChild...
              // must use first/lastChild instead
              // TODO: can this be .... removed?
              done_target =
                wrapper_selector[wrapper.target_selector + "ElementChild"]
                  || wrapper_selector[wrapper.target_selector + "Child"]
                  || wrapper_selector;
              done_target.appendChild(response);
            }
            content = wrapper_selector || response;


            // TODO: remove reverse, only used for widget.form captcha!
            // TODO: refactory this whole block
            if (wrapper.reverse) {
              wrapper.child_selector.insertBefore(
                content,
                wrapper.child_selector.firstChild
              );
            } else {
              // add content to DOM, page, or child_selector/fragment
              if (wrapper.add_on_dom) {
                last = document.body.lastChild;
                if (util.testForString("ui-footer", last.className)) {
                  document.body.insertBefore(wrapper.fragment, last);
                } else {
                  document.body.insertBefore(
                    content,
                    document.body.children[0]
                  );
                }
              } else {
                // TODO: terrible. what about footer/header/popup.
                if (content.querySelector("div.panel")) {
                  wrapper.fragment.appendChild(content);
                } else {
                  (wrapper.child_selector || wrapper.fragment)
                    .appendChild(content);
                }
              }
            }
          }
        }

        // translate
        if (wrapper.fragment) {
          map.actions.translateNodeList(wrapper.fragment);
        }

        // only for dynamic content
        if (!pass.skip && !quirk_dict.no_content) {
          // UPDATES - same %&/( as above
          if (quirk_dict.update) {
            selector = pass.state.gadget;
            update_target = widget.querySelector("[data-update]") ||
                widget.getElementsByTagName("form")[0];

            // TODO: generic method!
            // TODO: or inherit reference to controlgroup without being
            // child of form?
            // rescue persistent elements from dump
            detach = document.createDocumentFragment();
            child_list = update_target.children;

            for (m = 0, len = child_list.length; m < len; m += 1) {
              last_child = child_list[m];
              if (last_child.getAttribute("data-persist")) {
                detach.appendChild(last_child.cloneNode(true));
              }
            }

            //empty target, add new dynamic content and detached elements
            util.deleteChildren(update_target);
            update_target.appendChild(wrapper.fragment);
            update_target.appendChild(detach);

          // CREATE
          } else {
            selector = wrapper.fragment.firstElementChild ||
                wrapper.fragment.firstChild || wrapper.fragment;

            // TODO: find way to dig down into tree
            if (util.testForString("dyno", selector.className) === false) {
              selector = selector.firstChild;
            }
            pass.state.gadget = selector;
          }

          // preserve initial search other than _id
          if (pass.state.query.restore) {
            pass.state.restore = pass.state.query.restore;
          }

          // if a callback is provided, set it on state
          // TODO: once containers work, submit_to should change the container
          // vs the whole page?
          if (pass.config_dict.property_dict.submit_to) {
            pass.state.callback = pass.config_dict.property_dict.submit_to;
          }

          // tuck to gadget
          // WARNING: this should use data(), it BAD to store like this
          selector.state = pass.state;

          // not auth, no mojo!
          if (pass.grant || pass.url_dict.mode === "new") {
            app.setInfo(
              pass.create ? wrapper.fragment : selector,
              pass.state.query,
              pass.state.total,
              pass.state.selected
            );
          }
        }

        // change to new page if it is not active
        // TODO: no appending here if possible!?!
        // TODO: make sure this works for updates, too!!
        if (wrapper.is_page) {
          active = util.getPage().getAttribute("data-url");
          if (quirk_dict.create === true) {
            document.body.appendChild(wrapper.fragment);
            app.setPageTitle(quirk_dict.title_i18n);
          }

          encoded = window.encodeURIComponent(
            (pass.url_dict.data_url || "").split("?")[0]
          );
          if (encoded && encoded !== active) {
            $.mobile.changePage("#" + encoded);
          }
        // append to DOM if scoped
        } else if (pass.url_dict.data_url) {
          util.getPage(pass.url_dict.data_url).appendChild(wrapper.fragment);
          return RSVP.resolve(wrapper.placeholder || undefined);
        }

        // add placeholder if exists
        if (wrapper.placeholder) {
          wrapper.fragment.appendChild(wrapper.placeholder);
        }

        // return fragment
        return RSVP.resolve(wrapper.fragment || undefined);
      })
      .fail(app.util.error);
  };

  /* ********************************************************************** */
  /*                            APP UTIL                                    */
  /* ********************************************************************** */
  app.util = {};

  /** Fetch a subordinate record to set values of an object
   * @method setSubordinate
   * @method setParam
   * @param {object} kid Element configuration object
   * @param {object} wrapper Wrapping document
   * @returns {object} kid
   */
  // TODO: change setParams into scheme like structure to use same handler?
  app.util.setSubordinate = function (element, wrapper) {
    var i, len, param_list, param, promise_list, field, pass, data;

    param_list = element.logic.setParam;
    promise_list = [];
    data = wrapper.property_dict.data;

    // NOTE: data may be undefined - no records because of missing link
    // NOTE: throw?
    if (data) {
      for (i = 0, len = param_list.length; i < len; i += 1) {
        param = param_list[i];

        // NOTE: all regular params should be deleted by now!
        field = param[1];
        pass = {
          "field": field,
          "relation": data.doc[field],
          "subordinate": field.split("subordinate_")[1]
        };
        promise_list[i] = storage.subordinate(pass);
      }
    }

    return RSVP.all(promise_list)
      .then(function (response_list) {
        var j, k, res_len, response, param_len, out_param, val;

        // TODO: CUSTOM CODE, wrap in callback and make generic handler
        for (j = 0, res_len = response_list.length; j < res_len; j += 1) {
          response = response_list[j];
          for (k = 0, param_len = param_list.length; k < param_len; k += 1) {
            out_param = param_list[k];
            if (response.field === out_param[1]) {
              val = out_param[0];
              element.logic[val] = element.logic[val] || "";
              element.logic[val] += response.value;
            }
          }
        }
        return element;
      })
      .fail(app.util.error);
  };

  /** Set parameters on an object or flag object for subordination =
   * fetch related record to get value
   * @method setParam
   * @param {object} kid Element configuration object
   * @param {object} wrapper Wrapping document
   * @returns {object} kid
   */
  // TODO: clean up, no exceptions (location), clear key + val!
  // TODO: make lookup parsing robust!
  app.util.setParam = function (kid, wrapper) {
    var i, len, param_list, param, href, loc, splitter, key, val, lookup,
      new_val;

    param_list = kid.logic.setParam;
    for (i = 0, len = param_list.length; i < len; i += 1) {
      param = param_list[i];
      val = param[1];
      key = param[0];
      switch (val) {

      // NOTE: hacked for oauth redirect url
      // TODO: find way to include encoding when setting a href
      case "location":
        href = kid.direct.href;
        splitter = href.indexOf("?") > 0 ? "&" : "?";
        loc = window.location;
        kid.direct.href += splitter + window.encodeURIComponent(key) +
          "=" + window.encodeURIComponent(
            loc.origin + loc.pathname + loc.hash.split("?")[0]
          );
        break;

      // set values or flag for subordination inside map.element
      default:
        kid.logic[key] = kid.logic[key] || "";
        if (val.split("subordinate_").length > 1) {

          // NOTE: try to get value from state/URL before flagging
          // NOTE: must check for equality, otherwise id foo > foofoo
          // TODO: this is still super-not-robust... redo!
          lookup = wrapper.property_dict.state.query.query;
          if (lookup && lookup.indexOf(val) > -1) {
            new_val = lookup.split(val)[1].split("+")[0]
              .replace(":=", "").replace(")", "");
            if (kid.logic[key] !== new_val) {
              kid.logic[key] += new_val;
            }
          } else {
            kid.needs_subordination = true;
          }
        } else {
          kid.logic[key] += wrapper.property_dict.data.doc[val];
          delete kid.logic.setParam[i];
        }
        break;
      }
    }
    return kid;
  };

  /**
   * Parse url query parameter into storage query object
   * Expects the following URL format:
   *
   *      #foo&
   *      query:id=bar+foo=baz&
   *      limit:start=0+items=5&
   *      sort:id=ascending+foo=descending&
   *      select:id+foo+baz+cous
   *
   * @method parseUrlQuery
   * @param {string} str String to parse
   * @returns {object} Query to run based on parameters
   */
  app.util.parseQueryParameter = function (str) {
    var i, param_list, param_len, param, initial_query, indicator;

    param_list = str.split("&");
    initial_query = {};

    for (i = 0, param_len = param_list.length; i < param_len; i += 1) {
      param = param_list[i].split(":");
      indicator = param[0];
      switch (indicator) {
      case "query":
        initial_query.query = param[1].replace("=", ":=", "g")
          .replace("+", " AND ", "g");
        break;
      case "limit":
        // NOTE: Thanks Tristan!
        initial_query.limit = param[1].split("+")
          .map(function (part) { return part.split("=")[1]; });
        break;
      case "sort":
        initial_query.sort_on = param[1].split("+").map(function (part) {
          var s = part.split("=");
          return [s[0], s[1]];
        });
        break;
      case "select":
        initial_query.select_list = param[1].replace("+", ",", "g");
        break;
      }
    }
    return initial_query;
  };

  /**
   * Parse a clicked link to determine which page to load
   * @method parseLink
   * @param {string} url Url being navigated to
   * @return {object} navigation object
   **/
  app.util.parseLink = function (url) {
    var hash, path, clean_hash, decode, root, last, stripped, url_query,
      strip;

    hash = $.mobile.path.parseUrl(
      url.replace($.mobile.dialogHashKey, "")
    ).hash.replace("#", "");
    // decode = /^[^\/]*%2[^\/]*$/.test(hash);
    decode = /%[0-9a-f]{2}/i.test(hash);

    // decode (allowing URI encoded identifiers)
    if (decode) {
      clean_hash = window.decodeURIComponent(hash);
    } else {
      clean_hash = hash;
    }

    // query parameters
    strip = clean_hash.split("&");
    stripped = strip[0];

    if (strip.length > 1) {
      url_query = app.util.parseQueryParameter(strip[1]);
    }

    if (stripped === "") {
      root = util.getPage().getAttribute("data-url");
      return {
        "data_url": root,
        "root": root
      };
    }
    // check for mode
    path = stripped.split("/");
    last = path.length - 1;

    return {
      "mode": path[last],
      "fragment_list": path,
      "url_query": url_query,
      "data_url": clean_hash,
      "layout_level": last,
      "deeplink": true,
      "root": path[0]
    };
  };

  /**
   * Generate a link based on hierarchy and record
   * @method generateLink
   * @param {object} spec Object containing query/link related info
   * @param {object} record Object containing data including link id
   * @return {string} href
   */
  app.util.generateLink = function (spec, id) {
    var level, core, separator, empty_string;

    empty_string = "";

    // external link
    if (spec.source) {
      return spec.source;
    }

    // link current segment parameter vs segment
    if ((spec.link_core || empty_string).split("%26").length > 1) {
      separator = empty_string;
    } else {
      separator = "/";
    }

    level = spec.layout_level || 0;
    core = spec.link_core || (spec.fragment_list ?
        spec.fragment_list.slice(0, level + 1).join("/") : empty_string);

    return ("#" + core + separator + window.encodeURIComponent(id));
  };

  /**
   * Main error handler
   * @method error
   * @param {object} err  Error object or message
   **/
  // TODO: something...
  app.util.error = function (err) {
    console.log(err);
  };

  /**
   * Retrieve application configuration from path specified on the
   * JavaScript file loaded (thx http://requirejs.org)
   * @method fetchPath
   * @param {string} attribute Attribute to find
   * @return {string} path
   **/
  app.util.fetchPath = function (attribute) {
    var i, path, script_list = document.getElementsByTagName("script");

    for (i = 0; i < script_list.length; i += 1) {
      path = script_list[i].getAttribute(attribute);
      if (path) {
        return path;
      }
    }
    return undefined;
  };

  /**
   * Show and hide the jQuery Mobile status message (loader/icon/message)
   * @method loader
   * @param {string} message The message to display in the loader
   * @param {string} msg_i18n lookup for status message
   * @param {string} theme background theme
   * @param {string} icon Which icon to display when overriding the loader
   */
  app.util.loader = function (message, msg_i18n, icon) {
    var text_to_display = i18n && msg_i18n ?
        map.actions.translateLookup(msg_i18n) : message;

    // hm... jQuery... hm string building
    // TODO: better way > this should be a module, defaulting back to JQM
    if (app.property_dict.loader) {
      $.mobile.loading(
        "show",
        {
          "text": "",
          "textVisible": true,
          "theme": app.property_dict.loader_theme,
          "html": "<span class='ui-icon-" +
              (icon ? (icon + " loader_icon") : "loading") + "'> </span>" +
                  "<h1 class='loader_message'>" + text_to_display + "</h1>"
        }
      );
      // allow multiple updates and remove after 1000ms
      util.clearTimer();

      app.timer = window.setTimeout(function () {
        $.mobile.loading("hide");
      }, 3000);
    } else {
      app.util.error("showStatus: Loader not enabled");
    }
  };

  /* ====================================================================== */
  /*                             APP INIT                                   */
  /* ====================================================================== */

  /**
   * Initialization object
   */
  app.init = {};

  /**
   * Method for testing features required for a module. Feature testing
   * depends on modernizr being available
   * @method testSupport
   * @param {Array} The properties to test for availability
   * @return {Boolean} true/falsely
   */
  app.init.testSupport = function (property_list) {
    var j, no_support, fail, message;

    if (property_list.length > 0) {
      if (Modernizr) {
        fail = "";
        for (j = 0; j < property_list.length; j += 1) {
          if (Modernizr[property_list[j]] === false) {
            no_support = true;
            fail += property_list[j] + "|";
          }
        }
        if (no_support) {
          message = fail.splice(0, -1);
          app.util.error("browser: " + message + " not supported");

          factory.util.noItems({"message": message});
          return false;
        }
        return true;
      }
      app.util.error("testSupport: Modernizr requested but undefined");
    }
    return true;
  };

  /**
   * Load configuration modules into application
   * @method config
   * @param {Object} spec The configuration for the module to load
   * @param {Promise} The promise
   */
  app.init.config = function (content_dict) {
    var i, j, arr, promise_list, len, feature, feature_len, name, dict,
      set, type, nav, language, target, initializer;

    arr = app.empty_array;
    promise_list = [];
    len = content_dict.length;

    for (i = 0; i < len; i += 1) {
      feature = content_dict[i];

      // test support
      if (app.init.testSupport(feature.modernizr || [])) {

        feature_len = (feature.scheme || arr).length;
        type = feature.type;
        initializer = feature.initializer;
        set = feature.set_on;
        if (set) {
          target = app[feature.set_on] = {};
        } else {
          target = app;
        }
        //target = (set ? app[set] = {} : undefined) || app;

        // merge properties on target
        target.property_dict = util.mergeObject(
          feature.property_dict || {},
          target.property_dict || {}
        );

        // NOTE: Set all globals on app and reference from there
        for (j = 0; j < feature_len; j += 1) {
          dict = feature.scheme[j];
          name = dict.set_name || dict.property_dict.application_name || type;

          // preserve definition
          target.property_dict[name] = dict.property_dict;

          // TODO: Force browser language for i18n - not generic, remove
          switch (feature.handle) {
          case "language":
            nav = window.navigator;
            language = nav.userLanguage || nav.language;
            if (language && target.property_dict.use_browser_language) {
              dict.fallbackLng = dict.lng;
              dict.lng = language;
            }
            break;
          }

          // set promise and access to this module
          target[name] = promise_list[j] = initializer ?
              window[type][feature.initializer](dict.property_dict) :
                  window[type];
        }
      }

      if ((feature.children || arr).length > 0) {
        promise_list[j + 1] = app.content.set(feature);
      }
    }
    return RSVP.all(promise_list);
  };

  /**
   * Dict monitoring js promises in process
   */
  app.init.registration_dict = {};

  /**
   * Inject CSS/JS files into document
   * thx renderJS http://git.erp5.org/gitweb/renderjs.git?js=1
   * @method declare
   * @param {string} The url to inject
   * @param {ext} The file extension
   * @return {Promise} The promise
   */
  app.init.declare = function (url, ext) {
    var result, el;

    if (app.init.registration_dict.hasOwnProperty(url)) {
      result = RSVP.resolve();
    } else {
      result = new RSVP.Promise(function (resolve, reject) {
        if (ext === "css") {
          el = factory.element(
            "link",
            {"rel": "stylesheet", "type": "text/css", "href": url}
          );
        } else {
          el = factory.element(
            "script",
            {"type": "text/javascript", "src": url}
          );
        }
        el.onload = function () {
          app.init.registration_dict[url] = null;
          resolve();
        };
        el.onerror = function (e) {
          reject(e);
        };
        document.head.appendChild(el);
      });
    }
    return result;
  };

  /**
   * cross-browser wrapper for DOMContentLoaded
   * Thx Diego Perini http://javascript.nwbox.com/ContentLoaded/
   * @method contentLoaded
   * @param {object} win Window
   * @param {method} fn Callback to run
   */
  app.init.contentLoaded = function (win, fn) {
    var done = false,
      top = true,
      doc = win.document,
      root = doc.documentElement,
      add = doc.addEventListener ? 'addEventListener' : 'attachEvent',
      rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
      pre = doc.addEventListener ? '' : 'on',
      init = function (e) {
        if (e.type === 'readystatechange' && doc.readyState !== 'complete') {
          return;
        }
        (e.type === 'load' ? win : doc)[rem](pre + e.type, init, false);
        if (!done) {
          done = true;
          fn.call(win, e.type || e);
        }
      },
      poll = function () {
        try {
          root.doScroll('left');
        } catch (e) {
          window.setTimeout(poll, 50);
          return;
        }
        init('poll');
      };

    if (doc.readyState === 'complete') {
      fn.call(win, 'lazy');
    } else {
      if (doc.createEventObject && root.doScroll) {
        try {
          top = !win.frameElement;
        } catch (ignore) {}
        if (top) {
          poll();
        }
      }
      doc[add](pre + 'DOMContentLoaded', init, false);
      doc[add](pre + 'readystatechange', init, false);
      win[add](pre + 'load', init, false);
    }
  };

  /* ====================================================================== */
  /*                             ENTRY POINT                                */
  /* ====================================================================== */

  /**
   * Initializer fetching configuration file to setup application
   * @method ContentLoaded
   */
  app.init.contentLoaded(window, function () {

    // shim <picture>
    document.createElement("picture");

    // TODO: Don't wipe. Wait for sync...!
    // NOTE: Don't clear if page is opened in popup (like when using oauth)
    if (window.opener === null) {
      window.localStorage.clear();
    }

    storage.load({
      "url": app.util.fetchPath("data-storage"),
      "optional": true,
      "skip": true
    })
      .then(app.init.config)
      .then(function () {
        return storage.load({
          "url": app.util.fetchPath("data-config")
        });
      })
      .then(app.init.config)
      .then(app.setGlobalBindings)
      .then(app.parsePage)
      .then(function () {
        // need one call here to initialize whats on the first page
        $(document).enhanceWithin();

        // picturefill... so much extra §$%&/
        window.picturefill();
      })
      .fail(app.util.error);
  });

}(window, document, jQuery));
