fcf.module({
  name: "fcf:NClient/Wrapper.js",
  module: function(){
    var NClient = fcf.prepareObject(fcf, "NClient");

    var stUpdatableWrappers = {};
    var stUpdateCounter     = 0;
    var stUpdateActions     = undefined;
    var stArgRecursion = 0;
    var stCallsRecursion = {};

    fcf.NDetails._attachWrapperCounter = {};
    fcf.NDetails._noWrapperChilds = {};

    function merge_get_by_ids(a_dst, a_element){
      let elements = fcf.select(a_element, "[id]");
      for(let i = 0; i < elements.length; ++i){
        let delem = fcf.select(a_dst, `[id='${elements[i].id}']`)[0];
        if (!delem)
          continue;
        while(delem && delem.parentElement != a_dst)
          delem = delem.parentElement;
        if (!delem)
          continue;
        for(let j = 0; j < a_dst.childNodes.length; ++j)
          if (a_dst.childNodes[j] == delem)
            return j;
      }
      return -1;
    }

    function merge_fill(a_dst, a_src){
      let rm = [];
      for(let i = 0; i < a_dst.attributes; ++i)
        if (a_src.attributes[a_dst.attributes[i].name] === undefined && a_dst.attributes[i].name !== "fcfevntid")
          rm.push(a_dst.attributes[i].name);
      for(let i = 0; i < rm.length; ++i)
        a_dst.removeAttribute(rm[i]);
      for(let i = 0; i < a_src.attributes.length; ++i) {
        if (a_dst.attributes.value != a_src.attributes[i].value)
          a_dst.setAttribute(a_src.attributes[i].name, a_src.attributes[i].value);
      }
    }

    function merge(a_dst, a_src){
      merge_fill(a_dst, a_src);

      let di = 0;
      for(let i = 0; i < a_src.childNodes.length; ++i) {
        let srcNode = a_src.childNodes[i];
        if (srcNode.nodeType == Node.TEXT_NODE){
          if (a_dst.childNodes[di] && a_dst.childNodes[di].nodeType == Node.TEXT_NODE){
            if (a_dst.childNodes[di].nodeValue != srcNode.nodeValue)
              a_dst.childNodes[di].nodeValue = srcNode.nodeValue;
            a_dst.childNodes[di].___fcf___merge = true;
            ++di;
          } else {
            a_dst.insertBefore(srcNode, a_dst.childNodes[di]);
            srcNode.___fcf___merge = true;
            --i;
            ++di;
          }
        } else {
          let dnext = -1;
          if (srcNode.hasAttribute && srcNode.hasAttribute("fcfmrj")) {
            let fcfmrj = srcNode.getAttribute("fcfmrj");
            for(let j = di; j < a_dst.childNodes.length; ++j){
              if (a_dst.childNodes[j].getAttribute && a_dst.childNodes[j].getAttribute("fcfmrj") == fcfmrj){
                dnext = j;
                break;
              }
            }
          } else if (!fcf.empty(srcNode.id)){
            for(let j = di; j < a_dst.childNodes.length; ++j){
              if (a_dst.childNodes[j].id == srcNode.id){
                dnext = j;
                break;
              }
            }
          } else {
            dnext = merge_get_by_ids(a_dst, srcNode);
          }

          if (dnext == -1){
            a_dst.insertBefore(srcNode, a_dst.childNodes[di]);
            srcNode.___fcf___merge = true;
            --i;
            ++di
          } else {
            di = dnext;
            if (srcNode.hasAttribute && srcNode.hasAttribute("fcfmrj")) {
              merge(a_dst.childNodes[di], srcNode);
              a_dst.childNodes[di].___fcf___merge = true;
              ++di;
            } else if (fcf.select(srcNode, "[fcfmrj]")[0]) {
              merge(a_dst.childNodes[di], srcNode);
              a_dst.childNodes[di].___fcf___merge = true;
              ++di;
            } else {
              a_dst.insertBefore(srcNode, a_dst.childNodes[di]);
              srcNode.___fcf___merge = true;
              --i;
              ++di;
            }
          }
        }
      }

      for(let i = 0; i < a_dst.childNodes.length; ++i) {
        if (a_dst.childNodes[i].___fcf___merge){
          delete a_dst.childNodes[i].___fcf___merge;
        } else {
          a_dst.removeChild(a_dst.childNodes[i]);
          --i;
        }
      }
    }

    function insertTemplate(a_dstNode, a_template){
      let lstItems = fcf.select(a_dstNode, "[fcftemplate]");
      let lstId    = a_dstNode.id;
      let container = document.createElement("div");
      container.innerHTML = a_template.content;
      if (a_template.state.merge){
        merge(a_dstNode, container.firstChild);
      } else {
        let next = a_dstNode.nextSibling;
        let parent = a_dstNode.parentElement;
        a_dstNode.parentElement.removeChild(a_dstNode);
        parent.insertBefore(container.firstChild, next);
      }

      for(let i = 0; i < lstItems.length; ++i) {
        if (!document.getElementById(lstItems[i].id)) {
          let w = fcf.getWrapper(lstItems[i].id);
          if (w)
            w.destroy();
        }
      }
      if (a_dstNode.id != lstId){
        let w = fcf.getWrapper(lstId);
        if (w)
          w.destroy();
      }
    }

    function fillExternalRef(a_dst, a_rootId, a_id){
      let srcArgs = fcf.application.getLocalData().getSourceObject(a_id);
      if (srcArgs) {
        let objectValues = fcf.application.getLocalData().getObject(a_id);
        let parentWrapper   = fcf.getWrapper(a_id).getParent();
        let refParentObjectValues = parentWrapper ? fcf.application.getLocalData().getObject(parentWrapper.getId()) : undefined;

        if (Array.isArray(srcArgs.fcfReferences)){
          for(let i = 0; i < srcArgs.fcfReferences.length; ++i){
            let refId   = fcf.tokenize(srcArgs.fcfReferences[i][0], {args: objectValues, parent: refParentObjectValues});
            let refArg  = fcf.tokenize(srcArgs.fcfReferences[i][1], {args: objectValues, parent: refParentObjectValues});
            if (!a_dst[refId])
              a_dst[refId] = {};

            let refDst = fcf.resolveEx(a_dst[refId], refArg, true);
            let wrp = fcf.getWrapper(refId);
            if (wrp)
              refDst.object[refDst.key] = wrp.getArg(refArg);
          }
        }

        for(let key in srcArgs){
          if (key == "fcfRuntime")
            continue;
          if (!fcf.isArg(srcArgs[key]) || srcArgs[key].type !== "reference")
            continue;

          let refId   = fcf.tokenize(srcArgs[key].object.id, {args: objectValues, parent: refParentObjectValues});
          let refArg  = fcf.tokenize(srcArgs[key].arg, {args: objectValues, parent: refParentObjectValues});
          let isInner = fcf.getWrapper(a_rootId).select("[id="+refId+"]").length != 0;
          if (isInner)
            continue;

          if (!a_dst[refId])
            a_dst[refId] = {};

          let refDst = fcf.resolveEx(a_dst[refId], refArg, true);
          let wrp = fcf.getWrapper(refId);
          if (wrp)
            refDst.object[refDst.key] = wrp.getArg(refArg);
          else
            refDst.object[refDst.key] = undefined;
        }
      }
    }

    function recalculate(a_id){
      let wrapper    = fcf.getWrapper(a_id);
      let theme      = fcf.application.getTheme(wrapper.getArg("fcfTheme"));
      let sources    = fcf.application.getLocalData().getSourceObject(a_id);
      let modifyKeys = fcf.application.getLocalData().getModifyKeys(a_id);
      if (!modifyKeys)
        modifyKeys = {};
      let depsKeys = {};

      for(let key in sources){
        if (!fcf.isArg(sources[key]) || sources[key].type != "value")
          continue;
        if (key in modifyKeys)
          continue;
        depsKeys[key] = {};
        let deps = fcf.getVariablesString(sources[key].value);
        for(let i = 0; i < deps.length; ++i)
          if (!depsKeys[key][deps[i]])
            depsKeys[key][deps[i]] = fcf.parseObjectAddress(deps[i]);
      }
      let orderKeys = [];
      let processedKeys = {};
      while(true) {
        let fill = true;
        for(let key in depsKeys) {
          let free = true;
          for(let dep in depsKeys[key]){
            let depArr = depsKeys[key][dep];
            if (depArr[0] != "args")
              continue;
            if (depArr[1] in depsKeys){
              free = false;
              break;
            }
          }
          if (free) {
            fill = false;
            orderKeys.push(key);
            delete depsKeys[key];
          }
        }
        if (fill){
          for(let key in depsKeys)
            orderKeys.push(key);
          break;
        }
      }

      if (!orderKeys.length)
        return;

      let tokenizeData = {
        args:   wrapper.getArgs(),
        route:  fcf.getContext().route,
        decor:  theme.getDecor(),
      };

      for(let i =0; i < orderKeys.length; ++i){
        let key = orderKeys[i];
        if (fcf.isArg(sources[key]) && sources[key].type === "value"){
          tokenizeData.args[key] = fcf.tokenize(sources[key].value, tokenizeData);
          fcf.application.getLocalData().setItem(a_id, key, tokenizeData.args[key], true);
        }
      }
    }

    function rerender(a_id, a_template, a_theme, a_args, a_restoreState, a_resotreOriginState, a_url, a_updateOriginState){
      let externalRefValues = {};
      let wrapper = fcf.getWrapper(a_id);

      var modifyChildArgs = {
        childs: {},
        args: {},
      };
      if (wrapper) {
        fillExternalRef(externalRefValues, a_id, a_id);
        var childs = wrapper.getChilds();
        function clFillChilds(a_wrapper, a_modifyChildArgs, a_ignoreFillData){
          var fcfSaveChildsState = a_wrapper.getArg("fcfSaveChildsState");
          if (fcfSaveChildsState === false || (!a_restoreState && !a_resotreOriginState))
            a_ignoreFillData = true;

          fcf.each(a_wrapper.getChilds(), function(a_key, a_child){
            var fcfCallPosition = a_child.getArg("fcfCP");
            var id = a_child.getId();
            var modifyKeys = fcf.application.getLocalData().getModifyKeys(id);
            var args = {};
            var fcfMultiple = a_child.getArg("fcfMultiple");


            let clientRendering    = a_child.getOptions().clientRendering;
            if (clientRendering){
              let clientRenderingArg = a_child.getArg("fcfClientRendering");
              if (clientRenderingArg !== undefined)
                clientRendering = clientRenderingArg;
            }

            fillExternalRef(externalRefValues, a_id, id);

            if (!fcfMultiple && !a_ignoreFillData && a_restoreState && !a_resotreOriginState) {
              let sources = fcf.application.getLocalData().getOriginSourceObject(id);
              for(var key in sources) {
                if (modifyKeys && key in modifyKeys)
                  args[key] = sources[key];
                if (clientRendering === "update_np" && fcf.isArg(sources[key]) && sources[key].type === "programmable")
                  args[key] = a_child.getArg(key);
              }
              for(var key in modifyKeys){
                let isArg = fcf.isArg(sources[key]);
                let type  = isArg ? sources[key].type : undefined;
                if(isArg && (type == "url" || type == "reference" || (type != "programmable" && sources[key].important)))
                  continue;
                args[key] = a_child.getArg(key);
              }
            } else if (!fcfMultiple && !a_ignoreFillData && a_resotreOriginState){
              let sources  = fcf.application.getLocalData().getOriginSourceObject(id);
              let origArgs = fcf.application.getLocalData().getOriginObject(id);
              for(let key in origArgs){
                let type = fcf.isArg(sources[key]) ? sources[key].type : undefined;
                if(type && (type == "url" || type == "reference"))
                  args[key] = sources[key];
                else
                  args[key] = origArgs[key];
              }
            }

            if (!fcfMultiple && !a_ignoreFillData) {
              args["fcfId"] = a_child.getId();
              var fcfevntid = a_child.getDomElement().getAttribute("fcfevntid");
              if (fcfevntid)
                args["fcfEvntid"] = fcfevntid;

              a_modifyChildArgs.childs[fcfCallPosition] = {
                childs: {},
                args: args,
              }
            }

            clFillChilds(a_child, a_modifyChildArgs && a_modifyChildArgs.childs ? a_modifyChildArgs.childs[fcfCallPosition]: {}, fcfMultiple || a_ignoreFillData);
          });
        }
        clFillChilds(wrapper, modifyChildArgs);
        a_args.fcfChildsArgs = modifyChildArgs;
      }
      return fcf.application.render({
        template:          a_template,
        theme:             a_theme,
        owner:             false,
        args:              a_args,
        update:            true,
        wrapper:           false,
        url:               a_url,
        externalArgs:      externalRefValues,
        updateOriginState: a_updateOriginState,
      });
    }

    function stUpdateWrapper(a_info) {

      if (a_info.calcOnly){
        return rerender(a_info.id, a_info.template, a_info.theme, a_info.args, a_info.restoreState, a_info.restoreOriginState, a_info.url, a_info.updateOriginState)
        .then((a_template)=>{
          a_info.cb(undefined, a_template, true);
        })
        .catch((a_error) => {
          a_info.cb(a_error, undefined, true);
        })
      }

      if (!stUpdateActions)
        stUpdateActions = fcf.actions();

      ++stUpdateCounter;
      var updateCounterLast = stUpdateCounter;
      if (stUpdatableWrappers[a_info.id]){
        if (!a_info.cbs)
          a_info.cbs = [];
        if (stUpdatableWrappers[a_info.id].cb)
          a_info.cbs.push(stUpdatableWrappers[a_info.id].cb);
        fcf.append(a_info.cbs, stUpdatableWrappers[a_info.id].cbs);
      }
      stUpdatableWrappers[a_info.id] = a_info;

      function clUpdate() {
        if (updateCounterLast != stUpdateCounter){
          updateCounterLast = stUpdateCounter;
          setTimeout(clUpdate, 1);
          return;
        }

        var wrappers = stUpdatableWrappers;
        stUpdatableWrappers = {};

        var rm = {};
        function clFilter(a_ownerId, a_wrapper) {
          var chlids = a_wrapper.getChilds();
          for(var k in chlids) {
            var id = chlids[k].getId();
            if (id in wrappers){
              if (wrappers[a_ownerId]) {
                fcf.append(wrappers[a_ownerId].cbs, wrappers[id].cbs);
                if (!wrappers[a_ownerId].url && wrappers[id].url)
                  wrappers[a_ownerId].url = wrappers[id].url;
                wrappers[a_ownerId].cbs.push(wrappers[id].cb);
              }
              rm[id] = true;
            }
            clFilter(a_ownerId, chlids[k]);
          }
        }
        for(var id in wrappers){
          var wrapper = fcf.getWrapper(id);
          if (!wrapper){
            rm[id] = true;
            continue;
          }
          clFilter(id, wrapper);
        }
        for(var id in rm)
          delete wrappers[id];

        updateCounterLast = 0;


        fcf.each(wrappers, function(rootId, info){
          stUpdateActions.then((a_res, a_act)=>{
            fcf.actions()
            .then(()=>{
              let wrapper = fcf.getWrapper(rootId);
              wrapper.lock();
              return rerender(rootId, info.template, info.theme, info.args, info.restoreState, info.restoreOriginState, info.url, a_info.updateOriginState)
              .then((a_template)=>{
                wrapper.unlock();
                info.cb(undefined, a_template, true);
                for(let i = 0; i < info.cbs.length; ++i) {
                  info.cbs[i](
                    undefined,
                    {
                      state: {
                        page: a_template && a_template.state ? a_template.state.page : { header: {} },
                      },
                      route: a_template.route,
                    },
                    false);
                }
              })
            })
            .catch((a_error)=>{
              fcf.log.err("FCF:RENDER", a_error);
              wrapper.unlock();
              info.cb(a_error, undefined, true);
              for(let i = 0; i < info.cbs.length; ++i) {
                info.cbs[i](
                  a_error,
                  undefined,
                  false);
              }
            })
            .finally(()=>{
              a_act.complete();
            })
          });
        });
      }

      setTimeout(clUpdate, 0);
    }

    var stReferences = {};

    function stAttachReferences(a_wrapper){
      let id = a_wrapper.getId();
      let srcArgs = fcf.application.getLocalData().getSourceObject(id);
      let rmMap = {};
      let args = {};

      for(let name in srcArgs) {
        let arg = srcArgs[name];
        if (fcf.isArg(arg) && arg.type == "reference") {
          if (arg.object.id.indexOf("${{") !== -1 || arg.object.id.indexOf("@{{")  !== -1 || arg.arg.indexOf("${{")  !== -1 || arg.arg.indexOf("@{{")  !== -1 )
            arg = fcf.tokenize(arg, {args: fcf.application.getLocalData().getObject(a_wrapper._id), route: fcf.getContext().route})

          args[name] = arg;

          if (stReferences[arg.object.id]) {
            if (!rmMap[arg.object.id] || !rmMap[arg.object.id][id]) {
              if (!rmMap[arg.object.id])
                rmMap[arg.object.id] = {};
              rmMap[arg.object.id][id] = true;
              for(let an in stReferences[arg.object.id]) {
                if (stReferences[arg.object.id][an][id]) {
                  delete stReferences[arg.object.id][an][id];
                }
              }
            }
          }
        }
      }

      for(let name in args) {
        let arg = args[name];
        let argPath = fcf.normalizeObjectAddress(arg.arg);
        let argName = fcf.parseObjectAddress(arg.arg)[0];
        if (!stReferences[arg.object.id])
          stReferences[arg.object.id] = {};
        if (!stReferences[arg.object.id][argName])
          stReferences[arg.object.id][argName] = {};
        if (!stReferences[arg.object.id][argName][id])
          stReferences[arg.object.id][argName][id] = {};
        stReferences[arg.object.id][argName][id][name] = argPath;
      }
    }


    class Wrapper {
      constructor(a_initializeOptions, a_ignoreReg){
        this._id                   = a_initializeOptions.domElement
                                       ? a_initializeOptions.domElement.getAttribute("id")
                                       : undefined;
        this._initializeOptions    = a_initializeOptions;
        this._callPositionsSetArg  = {};
        this._callPositionsCounter = 0;
        this._destroy              = false;
        this._lockCounter          = 0;

        if (!a_ignoreReg)
          fcf.NDetails._wrappers[this._id] = this;

        let element = this.getDomElement();
        if (element && !element.getAttribute("fcfparent")){
          let parent = fcf.getWrapper(element.parentNode);
          if (parent){
            element.setAttribute("fcfparent", parent.getId());
            this.setArg("fcfParent", parent.getId(), true);
          }
        }
      }

      async initialize() {
        stAttachReferences(this);
        await fcf.liven(undefined, this._id);
        this._attachInnerEvents();
        await this.attach();
      }

      async _reattach() {
        let self = this;
        fcf.removeDomListener(this);

        let element = this.getDomElement();
        if (element && !element.getAttribute("fcfparent")){
          let parent = fcf.getWrapper(element.parentNode);
          if (parent){
            element.setAttribute("fcfparent", parent.getId());
            this.setArg("fcfParent", parent.getId(), true);
          }
        }

        stAttachReferences(this);

        try {
          await fcf.liven(undefined, this._id);
          this._attachInnerEvents();
          await self.attach();
        } catch(e){
          fcf.log.err("FCF", "Failed reattach: ", e);
          throw e;
        }
      }

      recalculate(){
        recalculate(this.getId());
      }

      update(a_options) {
        let self = this;
        if (!this.getDomElement())
          return fcf.actions().then(()=>{ return { page: { header: {} } }; });
        var alias      = this.getDomElement().getAttribute("fcfalias");
        var template   = this.getDomElement().getAttribute("fcftemplate");
        var parentId   = this.getDomElement().getAttribute("fcfparent");
        if (parentId === null)
          parentId = undefined;
        var id         = this.getDomElement().getAttribute("id");
        var args       = fcf.append({}, fcf.application.getLocalData().getSourceObject(id));

        let clientRendering = this.getOptions().clientRendering;
        if (clientRendering) {
          let clientRenderingArg = this.getArg("fcfClientRendering");
          if (clientRenderingArg !== undefined)
            clientRendering = clientRenderingArg;
        }
        if (clientRendering === "update_np")
          for (let key in args)
            if (fcf.isArg(args[key]) && args[key].type === "programmable")
              args[key] = fcf.application.getLocalData().getItem(id, key);

        fcf.each(fcf.application.getLocalData().getModifyKeys(id), function(a_key){
          let srcArg = fcf.application.getLocalData().getSourceItem(id, a_key);
          if (fcf.isArg(srcArg) && srcArg.type == "url")
            return;
          args[a_key] = fcf.application.getLocalData().getItem(id, a_key);
        });

        if (!fcf.empty(alias))
          args.fcfAlias = alias;
        args.fcfId = id;
        args.fcfParent = parentId;
        if (this.getDomElement().getAttribute("fcfevntid"))
          args.fcfEvntid = this.getDomElement().getAttribute("fcfevntid");

        delete args.fcfRuntime;

        return fcf.actions()
        .then((a_res, a_act)=>{
          var updateInfo = {
            id:                 self.getId(),
            template:           template,
            args:               args,
            restoreState:       typeof a_options !== "object" || !("restoreChilds" in a_options) || a_options.restoreChilds,
            restoreOriginState: false,
            url:                a_options && a_options.url,
            calcOnly:           a_options && a_options.calcOnly,
            cbs:                [],
            cb: function(a_error, a_template, a_rootCallback){
              if (a_error){
                a_act.error(a_error);
                return;
              }

              if (!a_rootCallback || (a_options && a_options.calcOnly)){
                a_act.complete({content: a_template.content, route: a_template.route, page: a_template.state.page});
                return;
              }

              var domElement = self.getDomElement();
              if (!domElement){
                a_act.complete({content: a_template.content, route: a_template.route, page: a_template.state.page});
                return;
              }

              let activeElement = document.activeElement;
              let activeElementTag = activeElement ? activeElement.tagName : undefined;
              let wrapper = fcf.getWrapper(activeElement);
              let wrapperId = wrapper ? wrapper.getId() : undefined;
              let focusElementNumber = -1;
              let selectionStart = 0;

              if (wrapperId == self.getId() || self.select("[id='" + wrapperId + "']").length){
                selectionStart = activeElement.selectionEnd;
                let elements = activeElementTag ? self.select(activeElementTag) : [];
                for(let i = 0; i < elements.length; ++i) {
                  ++focusElementNumber;
                  if (elements[i] == activeElement)
                    break;
                }
              }

              insertTemplate(domElement, a_template);

              if (focusElementNumber != -1) {
                let elements = activeElementTag ? self.select(activeElementTag) : undefined;
                for(let i = 0; i < elements.length; ++i) {
                  if (i == focusElementNumber){
                    if (!fcf.empty(selectionStart))
                      elements[i].selectionStart = selectionStart;
                    elements[i].focus();
                    break;
                  }
                }
              }

              fcf.liven(domElement)
              .then(()=>{
                fcf.application.getEventChannel().send("fcf_render", {element: self.getDomElement()});
              })
              .then(()=>{
                a_act.complete({content: a_template.content, route: a_template.route, page: a_template.state.page});
              })
            }
          };
          stUpdateWrapper(updateInfo);
        });
      }

      refresh(a_options) {
        let self = this;
        if (!this.getDomElement())
          return fcf.actions().then(()=>{ return { page: { header: {} } }; });

        let alias      = this.getDomElement().getAttribute("fcfalias");
        let template   = this.getDomElement().getAttribute("fcftemplate");
        let parentId   = this.getDomElement().getAttribute("fcfparent");
        if (parentId === null)
          parentId = undefined;
        let id         = this.getDomElement().getAttribute("id");
        let args       = {};
        let sources    = fcf.application.getLocalData().getOriginSourceObject(id);
        let originSourcesKeys = fcf.application.getLocalData().getOriginSourcesKeys(id);
        fcf.each(originSourcesKeys, (i, k)=>{
          args[k] = sources[k];
        });

        let originData = fcf.application.getLocalData().getOriginObject(this.getId());
        for(let key in originData)
          args[key] = originData[key];

        if (!fcf.empty(alias))
          args.fcfAlias = alias;
        args.fcfId = id;
        args.fcfParent = parentId;
        if (this.getDomElement().getAttribute("fcfevntid"))
          args.fcfEvntid = this.getDomElement().getAttribute("fcfevntid");

        delete args.fcfRuntime;

        return fcf.actions()
        .then((a_res, a_act)=>{
          let updateInfo = {
            id:                 self.getId(),
            template:           template,
            args:               args,
            restoreState:       false,
            restoreOriginState: true,
            url:                a_options && a_options.url,
            cbs:                [],
            cb: function(a_error, a_template, a_rootCallback){
              if (!a_rootCallback){
                a_act.complete({page: a_template.state.page, route: a_template.route});
                return;
              }


              let domElement = self.getDomElement();
              if (!domElement){
                a_act.complete({page: a_template.state.page, route: a_template.route});
                return;
              }

              insertTemplate(domElement, a_template);

              fcf.liven(domElement)
              .then(()=>{
                fcf.application.getEventChannel().send("fcf_render", {element: self.getDomElement()});
              })
              .then(()=>{
                a_act.complete({page: a_template.state.page, route: a_template.route});
              });
            }
          };
          stUpdateWrapper(updateInfo);
        });
      }

      reloadArg(a_name) {

      }

      reload(a_options) {
        let self = this;
        if (!this.getDomElement())
          return fcf.actions().then(()=>{ return { page: { header: {} } }; });

        let alias      = this.getDomElement().getAttribute("fcfalias");
        let template   = this.getDomElement().getAttribute("fcftemplate");
        let parentId   = this.getDomElement().getAttribute("fcfparent");
        if (parentId === null)
          parentId = undefined;
        let id         = this.getDomElement().getAttribute("id");
        let args       = {};
        let sources    = fcf.application.getLocalData().getOriginSourceObject(id);
        let originSourcesKeys = fcf.application.getLocalData().getOriginSourcesKeys(id);
        fcf.each(originSourcesKeys, (i, k)=>{
          args[k] = sources[k];
        });
        if (!fcf.empty(alias))
          args.fcfAlias = alias;
        args.fcfId = id;
        args.fcfParent = parentId;
        if (this.getDomElement().getAttribute("fcfevntid"))
          args.fcfEvntid = this.getDomElement().getAttribute("fcfevntid");

        delete args.fcfRuntime;

        return fcf.actions()
        .then((a_res, a_act)=>{
          let updateInfo = {
            id:                 self.getId(),
            template:           template,
            args:               args,
            update:             true,
            restoreState:       false,
            restoreOriginState: false,
            updateOriginState:  true,
            url:                a_options && a_options.url,
            cbs:                [],
            cb: function(a_error, a_template, a_rootCallback){
              if (!a_rootCallback){
                a_act.complete({page: a_template.state.page, route: a_template.route});
                return;
              }

              let domElement = self.getDomElement();
              if (!domElement){
                a_act.complete({page: a_template.state.page, route: a_template.route});
                return;
              }

              insertTemplate(domElement, a_template);

              fcf.liven(domElement)
              .then(()=>{
                return fcf.application.getEventChannel().send("fcf_render", {element: self.getDomElement()});
              })
              .then(()=>{
                a_act.complete({page: a_template.state.page, route: a_template.route});
              });
            }
          };
          stUpdateWrapper(updateInfo);
        });
      }

      destroy(){
        if (this._destroy)
          return;
        this._destroy = true;
        if (typeof this.onDestroy === "function")
          this.onDestroy();
        fcf.removeDomListener(this);

        fcf.each(this.getChilds(), (a_key, a_child)=>{
          a_child.destroy();
        })

        var de = this.getDomElement();
        if (de)
          de.parentElement.removeChild(de);
        let id = this.getId();
        delete stReferences[id];
        delete fcf.NDetails._rwrappers[id];
        delete fcf.NDetails._wrappers[id];

        let fcfInitialStorageOfChildren = this.getArg("fcfInitialStorageOfChildren");
        let parent = this.getParent();
        let isRootInitialStorageOfChildren = (!parent || !parent.getArg("fcfInitialStorageOfChildren")) &&
                                             this.getArg("fcfRootInitialStorageOfChildren");
        if (isRootInitialStorageOfChildren || !fcfInitialStorageOfChildren) {
          fcf.each(fcf.NDetails._noWrapperChilds[this.getId()], (a_childId)=>{
            delete stReferences[a_childId];
            fcf.application.getLocalData().removeObject(a_childId);
          })

          fcf.application.getLocalData().removeObject(id);
        }
      }

      emit(a_event, a_exData){
        if (typeof a_event == "string"){
          let event = document.createEvent('Event');
          event.initEvent(a_event, true, true);
          event.name = a_event;
          if (!event.type)
            event.type = a_event;
          a_event = event;
        }
        if (a_exData)
          fcf.append(a_event, a_exData);
        return fcf.emitDomEvent(this.getActionDomElement(), a_event);
      }

      send(a_data, a_inputFiles, a_async, a_cb){
        let self = this;

        if (typeof a_inputFiles === "boolean"){
          a_cb = a_async;
          a_async = a_inputFiles;

        }

        if (typeof a_inputFiles === "function"){
          a_cb = a_inputFiles;
          a_inputFiles = undefined;
          a_async = true;
        }

        if (typeof a_async === "function"){
          a_cb = a_async;
          a_async = true;
        }

        return fcf.actions().then((a_res, a_act) => {
          const formData = new FormData();



          var filesAttributes = [];
          var counter = 0;
          fcf.each(a_inputFiles, function(k, input){
            let files = input instanceof File                                         ? [input] :
                        input instanceof HTMLElement && fcf.isEnumerable(input.files) ? input.files :
                                                                                        [];
            for(var i = 0; i < files.length; ++i){
              formData.append('files[' + (counter++) + ']', files[i]);
              var attributes = {};
              if (input instanceof HTMLElement)
                fcf.each(input.attributes, function(k, attr){ attributes[attr.name] = attr.value; });
              filesAttributes.push(attributes);
            }
          });

          function processFileVariables(a_object, a_path){
            for(let key in a_object) {
              if (typeof a_object !== "object" || !a_object)
                continue;
              let files = undefined;
              if (a_object[key] instanceof File)
                files = [a_object[key]];
              else if (a_object[key] instanceof HTMLElement)
                files = a_object[key].files;

              if (files) {
                let strPath = "";
                let path = [...a_path,key];
                for(let i= 0; i < path.length; ++i)
                  strPath += '["' + path[i] + '"]';

                for(var i = 0; i < files.length; ++i){
                  formData.append('files[' + (counter++) + ']', files[i]);
                  var attributes = {};
                  if (a_object[key] instanceof HTMLElement)
                    fcf.each(a_object[key].attributes, function(k, attr){ attributes[attr.name] = attr.value; });
                  attributes.objectPath = strPath;
                  filesAttributes.push(attributes);
                }
              } else {
                let newPath = [...a_path, key];
                processFileVariables(a_object[key], newPath);
              }
            }
          }
          processFileVariables(a_data, []);

          formData.append('filesAttributes', JSON.stringify(filesAttributes));
          formData.append('data', fcf.str(JSON.stringify(a_data)));

          fcf.load({
            path: "@url:fcfReceive?template=" + encodeURIComponent(self.getTemplate()),
            post: formData,
            async: a_async,
            onResult: function(a_error, a_response){
              if (a_cb)
                a_cb(a_error, a_response);
              if (a_error) {
                fcf.application.getEventChannel().send("fcf_error", {action: "method_send", error: a_error});
                a_act.error(a_error);
              } else {
                try {
                  a_response = a_response == "undefined" ? undefined : JSON.parse(a_response);
                } catch(e){ }
                a_act.complete(a_response);
              }
            }
          })
        });
      };

      getTemplate(){
        let el = this.getDomElement();
        return el ? el.getAttribute("fcftemplate") : undefined;
      }

      getId(){
        return this._id;
      }

      getAlias() {
        return this.getDomElement().getAttribute("fcfalias");
      }

      getDomElement(){
        return this != fcf.application.getRootWrapper() ? document.getElementById(this._id) : document.body;
      }

      getActionDomElement(){
        return this.getDomElement();
      }

      getParent(){
        if (!this.getDomElement())
          return;
        var parentId = this.getDomElement().getAttribute("fcfparent");
        if (fcf.empty(parentId))
          return this != fcf.application.getRootWrapper() ? fcf.application.getRootWrapper() : undefined;
        return fcf.getWrapper(parentId);
      }

      getChilds() {
        var domChilds = fcf.select("[fcfparent=" + this._id + "]");
        var result = [];
        for(var i = 0; i < domChilds.length; ++i) {
          var wrapper = fcf.NDetails._wrappers[domChilds[i].getAttribute("id")];
          if (wrapper)
            result.push(wrapper);
        }
        return result;
      }

      getFirstChild() {
        var domChilds = fcf.select(document.getElementById(this._id), "[fcfparent=" + this._id + "]");
        for(var i = 0; i < domChilds.length; ++i) {
          var wrapper = fcf.NDetails._wrappers[domChilds[i].getAttribute("id")];
          return wrapper;
        }
        return;
      }

      getChild(a_alias) {
        var domChild = fcf.first(fcf.select(document.body, "[fcfparent=\"" + this._id + "\"][fcfalias=\"" + fcf.encodeHtml(a_alias) + "\"]"));
        if (!domChild)
          return;
        return fcf.NDetails._wrappers[domChild.getAttribute("id")];
      }

      isChild(a_wrapper){
        a_wrapper = a_wrapper.getParent();
        while(a_wrapper) {
          if (a_wrapper == this)
            return true;
          a_wrapper = a_wrapper.getParent();
        }
        return false;
      }

      isOwn(a_wrapper){
        return this.isChild(a_wrapper) || a_wrapper === this;
      }

      select(a_selector){
        return fcf.select(`#${this._id} ${a_selector}`);
      }

      processWritenValue(a_valuesInfo, a_defaultLanguage){
        if (this.getArg("translate"))
          return;
        for(let lang in a_valuesInfo)
          if (lang != a_defaultLanguage)
            a_valuesInfo[lang].ignore = true;
      }

      getOptions(){
        let info = fcf.NDetails.renderInstructions[this.getTemplate()];
        return info && info.options ? info.options : {};
      }

      getArg(a_key, a_realData){
        return fcf.application.getLocalData().getItem(this._id, a_key, !a_realData);
      }

      getArgs() {
        let self = this;
        let result = {};
        fcf.each(fcf.application.getLocalData().getSourceObject(this._id), (a_key)=>{
          result[a_key] = self.getArg(a_key);
        });
        fcf.each(fcf.application.getLocalData().getModifyKeys(this._id), (a_index, a_key)=>{
          if (!(a_key in result))
            result[a_key] = self.getArg(a_key);
        });
        return result;
      }

      getShortArgs() {
        return fcf.application.getLocalData().getObject(this._id);
      }

      setArg(a_key, a_value, a_ignoreCallbacks, a_ignoreRedrawing, a_innerCall, a_editor, a_ignoreURLArgs){
        if (!a_editor)
          a_editor = this;
        let callPosition = a_key + ":" + this.getId();
        this._setArgImpl(a_key, a_value, a_ignoreCallbacks, a_ignoreRedrawing, a_innerCall, callPosition, a_editor, a_ignoreURLArgs);
      }

      validate(a_value, a_options, a_dsterros) {
        let childs = this.getChilds();
        for (let i = 0; i < childs.length; ++i)
          childs[i].validate(typeof a_value == "object" ? a_value[childs[i].getAlias()] : undefined, a_options, a_dsterros);
      }

      check() {
        let value = this.getArg("value");
        let errors = [];
        this.validate(value, {}, errors);
        return errors;
      }

      async lock(){
        if (++this._lockCounter > 1)
          return;
        let wltp = this.getArg("fcfLockTemplate") === true                                                  ? "@controls:lock" :
                   this.getArg("fcfLockTemplate") !== undefined                                             ? this.getArg("fcfLockTemplate") :
                   this.getOptions().lockTemplate !== true && this.getOptions().lockTemplate !== undefined  ? this.getOptions().lockTemplate :
                                                                                                              "@controls:lock";
        if (wltp === "none")
          return;
        let uuid = fcf.uuid();
        this._wrapperLocker = fcf.locker({selector: "#" + this.getId(), class: "fcf-update"});
        this._wrapperLocker.uuid = uuid;
        let div    = document.createElement("div");
        div.innerHTML = "&nbsp;";
        this._wrapperLocker.insertBefore(div, this._wrapperLocker.firstChild);
        let rectb = this._wrapperLocker.getBoundingClientRect();
        if (rectb.y < 0){
          div.style.top = "0px";
          div.style.position = "sticky";
        }
        if (wltp) {
          let template = await fcf.application.render({template: wltp});
          if (this._wrapperLocker && this._wrapperLocker.uuid == uuid){
            div.innerHTML = template.content;
            fcf.liven(div);
          }
        }

      }

      unlock(){
        if (this._lockCounter <= 0)
          return;
        --this._lockCounter;
        if (this._lockCounter > 0)
          return;
        if (this._wrapperLocker) {
          this._wrapperLocker.remove();
          this._wrapperLocker = undefined;
        }
      }

      attach(){
        let self = this;
        let scripts = [];


        if (!fcf.application.isRun() || fcf.application.getRootWrapper() === this)
          return fcf.actions();

        if (this._wrapperLocker){
          this.getDomElement().appendChild(this._wrapperLocker);
          this._wrapperLocker.resize();
        }

        if (!(this.getId() in fcf.NDetails._attachWrapperCounter))
          fcf.NDetails._attachWrapperCounter[this.getId()] = 1;
        else
          ++fcf.NDetails._attachWrapperCounter[this.getId()];
        let currentAttachCounter = fcf.NDetails._attachWrapperCounter[this.getId()];
        fcf.each(fcf.NDetails._noWrapperChilds[this.getId()], (a_childId, a_attachCounter)=>{
          if (a_attachCounter+1 < currentAttachCounter){
            delete stReferences[a_childId];
            fcf.application.getLocalData().removeObject(a_childId);
          }
        })


        fcf.each(this.select("script"), (a_key, a_script)=>{
          if (fcf.getWrapper(a_script) == self)
            scripts.push(a_script);
        });

        fcf.actions().asyncEach(scripts, async (a_key, a_script, a_res, a_act)=>{
          try {
            if (!fcf.empty(a_script.src)){
              const script = document.createElement('script');
              script.src = a_script.src;
              script.addEventListener('load', ()=>{
                a_act.complete();
              });
              script.addEventListener('error', (a_error)=>{
                a_act.complete();
                fcf.log.err("FCF", new fcf.Exception("ERROR", {error: "Failed execution script tag"}, a_error));
              });
              document.head.appendChild(script);
            } else {
              eval(a_script.innerHTML);
            }
          } catch(error){
            a_act.complete();
            fcf.log.err("FCF", new fcf.Exception("ERROR", {error: "Failed execution script tag"}, error))
          }
        });

        return fcf.actions();
      }

      _onArg(a_argName, a_value, a_editor, a_ignoreRedrawing, a_isInnerCall, a_suffix) {
        let autoUpdate = this.getArg("fcfAutoUpdate");
        if (autoUpdate === undefined){
          let options = this.getOptions();
          if (!options)
            return;
          autoUpdate = options.autoUpdate;

        }

        if (a_ignoreRedrawing)
          return;

        if (
            (
              (autoUpdate === true || autoUpdate === "all") ||
              (autoUpdate === "external" && !this.isChild(a_editor))
            ) &&
            (
              a_editor !== this || !a_isInnerCall
            )
         ) {
          this.update();
        } else {
          this._redrawRuntime(a_argName)
        }
      }

      _redrawRuntime(a_argName){

        function removeQuotes(a_string){
          a_string = fcf.trim(a_string);
          if (a_string[0] == "\"" || a_string[0] == "'")
            a_string = a_string.substring(1, a_string.length - 1);
          return a_string;
        }

        function styleNameToCodeName(a_name){
          let lpos = 0;
          let pos = a_name.indexOf("-");
          if (pos == -1)
            return a_name;
          let result = "";
          while(true){
            if (lpos != 0){
              result += a_name[lpos].toUpperCase();
              result += a_name.substring(lpos+1, pos != -1 ? pos : a_name.length);
            } else {
              result += a_name.substring(lpos, pos != -1 ? pos : a_name.length);
            }
            if (pos == -1)
              break;
            lpos = pos + 1;
            pos = a_name.indexOf("-", lpos);
          }
          return result;
        }

        function splitAttributes(a_str){
          let result = {};
          let p = 0;
          let l = a_str.length;
          while(p < l) {
            while(a_str.charCodeAt(p) <= 32 && p < l){
              ++p;
            }
            if (p >= l)
              break;
            let n = "";
            while(a_str.charCodeAt(p) > 32 && a_str[p] != "=" && p < l){
              n += a_str[p];
              ++p;
            }
            result[n] = "";
            if (p >= l)
              break;
            while(a_str.charCodeAt(p) <= 32 && p < l){
              ++p;
            }
            if (p >= l)
              break;
            if (a_str[p] != "=")
              continue;
            ++p;
            while(a_str.charCodeAt(p) <= 32 && p < l){
              ++p;
            }
            if (p >= l)
              break;
            let q = false;
            if (a_str[p] == "\"" || a_str[p] == "'"){
              q = a_str[p];
              ++p;
            }
            if (p >= l)
              break;
            let v = "";
            if (q) {
              while(a_str[p] != q && p < l){
                v += a_str[p];
                ++p;
              }
              ++p;
            } else {
              while(a_str.charCodeAt(p) > 32 && p < l){
                v += a_str[p];
                ++p;
              }
            }
            result[n] = v;

          }
          return result;
        }


        let renderInstructions = fcf.NDetails.renderInstructions[this.getTemplate()];
        if (!(a_argName in renderInstructions.runtimeArgs))
          return;
        let rids = renderInstructions.runtimeArgs[a_argName];
        let fcfRuntime = this.getArg("fcfRuntime");
        let classes = {};
        let tags = {};
        for(let i = 0; i < rids.length; ++i){
          let rid = rids[i];
          if (!fcfRuntime[rid])
            continue;
          let rir = renderInstructions.runtime[rid];
          tags[rid] = {};
          for(let rrid in fcfRuntime[rid]){
            let rrInfo = fcfRuntime[rid][rrid];
            for(let index = 0; index < rir.length && index < rrInfo.length; ++index) {
              let variables = { args: {} }
              for(let k in rrInfo[index].v){
                variables[k] = rrInfo[index].v[k];
              }
              for(let k = 0; k < rir[index].variables.length; ++k) {
                let karr = rir[index].variables[k].split(".");
                if (karr[0] == "args")
                  variables.args[karr[1]] = this.getArg(karr[1]);
              }
              if (rrInfo[index].t == 1 || rrInfo[index].t == 3) {
                let element = this.select("[rrid=\"" + rrid + "\"]")[0]
                element.innerHTML = fcf.scriptExecutor.parse(rir[index].code, variables, rir[index].code, 0, 0);
              } else if (rrInfo[index].t == 2) {
                tags[rid][rir[index].mainIndex] = rrid;
              }
            }
          }
        }

        for(let rid in tags){
          for(let mainIndex in tags[rid]) {
            let rrid = tags[rid][mainIndex];
            let mainStaticInfo  = renderInstructions.runtime[rid][mainIndex];
            let attributes = "";
            let oldAttributes = "";
            for(let i = 0; i < mainStaticInfo.attribute.length; ++i){
              if (typeof mainStaticInfo.attribute[i] == "number"){
                let index = mainStaticInfo.attribute[i];
                let vars = {args: {}};
                let staticInfo = renderInstructions.runtime[rid][index];
                for(let j = 0; j < staticInfo.variables.length; ++j){
                  let nameArr = staticInfo.variables[j].split(".");
                  if (nameArr[0] != "args")
                    continue;
                  vars.args[nameArr[1]] = this.getArg(nameArr[1]);
                }
                for(let varName in fcfRuntime[rid][rrid][index].v) {
                  vars[varName] = fcfRuntime[rid][rrid][index].v[varName];
                }
                let v = fcf.scriptExecutor.parse(staticInfo.code, vars, "RUNTIMET:" + staticInfo.code, 0, 0);
                attributes += v;
                oldAttributes += fcfRuntime[rid][rrid][index].c;
                fcfRuntime[rid][rrid][index].c = v;
              } else {
                attributes += mainStaticInfo.attribute[i];
                oldAttributes += mainStaticInfo.attribute[i];
              }
            }

            let attributesMap = splitAttributes(attributes);
            let oldAttributesMap = splitAttributes(oldAttributes);
            let allAttributesMap = fcf.append({}, attributesMap, oldAttributesMap);
            for(let an in allAttributesMap){
              let attributeName = an in attributesMap ? an : "";
              let oldAttributeName = an in oldAttributesMap ? an : "";
              let attributeValue = an in attributesMap ? attributesMap[an] : "";
              let oldAttributeValue = an in oldAttributesMap ? oldAttributesMap[an] : "";

              if (an == "class") {
                let curClasses = fcf.trim(attributeValue, ["\"", "'"]).split(/[\s]/);
                let curClassesMap = {};
                for(let i = 0; i < curClasses.length; ++i){
                  if (curClasses[i] == "")
                    continue;
                  curClassesMap[curClasses[i]] = true;
                }
                let oldClasses = fcf.trim(oldAttributeValue, ["\"", "'"]).split(/[\s]/);
                let oldClassesMap = {};
                for(let i = 0; i < oldClasses.length; ++i){
                  if (oldClasses[i] == "")
                    continue;
                  oldClassesMap[oldClasses[i]] = true;
                }
                let r = {};
                let a = {};
                for(let c in curClassesMap) {
                  if (!(c in oldClassesMap))
                    a[c] = true;
                }
                for(let c in oldClassesMap) {
                  if (!(c in curClassesMap))
                    r[c] = true;
                }
                let e = this.select("[rrid=\"" + rrid + "\"]")[0];
                for(let c in a){
                  try {
                    e.classList.add(c);
                  } catch (e){
                    fcf.log.err("FCF", "Failed add css class by calculation argument: ", e);
                  }
                }
                for(let c in r){
                  try {
                    e.classList.remove(c);
                  } catch (e){
                    fcf.log.err("FCF", "Failed remove css class by calculation argument: ", e);
                  }
                }
              } else if (an == "style") {
                let curStyleArrStr = attributeValue.split(";");
                let curStyleMap = {};
                for(let i = 0; i < curStyleArrStr.length; ++i) {
                  let dp = curStyleArrStr[i].indexOf(":");
                  if (dp == -1)
                    continue;
                  let n = removeQuotes(curStyleArrStr[i].substring(0, dp));
                  let v = removeQuotes(curStyleArrStr[i].substring(dp+1));
                  if (n == "" || v == "")
                    continue;
                  curStyleMap[n] = v;
                }
                let oldStyleArrStr = oldAttributeValue.split(";");
                let oldStyleMap = {};
                for(let i = 0; i < oldStyleArrStr.length; ++i) {
                  let dp = oldStyleArrStr[i].indexOf(":");
                  if (dp == -1)
                    continue;
                  let n = removeQuotes(oldStyleArrStr[i].substring(0, dp));
                  if (n == "")
                    continue;
                  oldStyleMap[n] = true;
                }
                let e = this.select("[rrid=\"" + rrid + "\"]")[0];
                for(let s in oldStyleMap){
                  if (!(s in curStyleMap)){
                    e.style[styleNameToCodeName(s)] = null;
                  }
                }
                for(let s in curStyleMap){
                  e.style[styleNameToCodeName(s)] = curStyleMap[s];
                }
              } else {
                let e = this.select("[rrid=\"" + rrid + "\"]")[0];
                if (attributeName != oldAttributeName) {
                  e.removeAttribute(oldAttributeName);
                }
                if (!fcf.empty(attributeName)){
                  e.setAttribute(attributeName, attributeValue);
                }
              }
            }
          } // end rrid
        } // end rid
      }

      _getOriginArgs() {
        let self = this;
        let result = {};
        fcf.each(fcf.application.getLocalData().getSourceObject(this._id), (a_key)=>{
          result[a_key] = self._getOriginArg(a_key);

        });
        return result;
      }

      _getOriginArg(a_key){
        return fcf.application.getLocalData().getOriginItem(this._id, a_key);
      }

      _setArg(a_key, a_value, a_ignoreCallbacks, a_ignoreRedrawing){
        this.setArg(a_key, a_value, a_ignoreCallbacks, a_ignoreRedrawing, true, this);
      }

      _setArgImpl(a_key, a_value, a_ignoreCallbacks, a_ignoreRedrawing, a_innerCall, a_callPosition, a_editor, a_ignoreURLArgs) {
        let self = this;

        if (a_callPosition in this._callPositionsSetArg){
          fcf.application.getLocalData().setItem(this._id, a_key, a_value);
          return;
        }
        ++this._callPositionsCounter;
        this._callPositionsSetArg[a_callPosition] = true

        let originValue = fcf.application.getLocalData().getItem(this._id, a_key);
        fcf.application.getLocalData().setItem(this._id, a_key, a_value);

        if (a_ignoreCallbacks){
          --this._callPositionsCounter;
          if (!this._callPositionsCounter)
            this._callPositionsSetArg = {};
          return;
        }

        let eventMap = {};

        let clGetArgInfo = (a_id, a_key) => {
          let parts = fcf.parseObjectAddress(a_key);
          let name = parts[0];
          let suffix = "";
          let source = fcf.application.getLocalData().getSourceItem(a_id, name);
          for(var i = 1; i < parts.length; ++i)
            suffix += '["' + parts[i] + '"]';

          if (fcf.isArg(source) && source.type == "reference"){
            if (source.object.id.indexOf("${{") !== -1 || source.object.id.indexOf("@{{")  !== -1 || source.arg.indexOf("${{")  !== -1 || source.arg.indexOf("@{{")  !== -1 ){
              source = fcf.tokenize(source, {args: fcf.application.getLocalData().getObject(a_id), route: fcf.getContext().route})
            }
          }

          return {
            path:   fcf.normalizeObjectAddress(a_key),
            arg:    name,
            suffix: suffix,
            id:     a_id,
            source: source,
            requestPath: "",
          };
        }


        let clProcessLink = (a_id, a_key, a_value, a_req, a_eventMap, a_protectDublicattes, a_isRoot, a_upDirection) => {
          a_key = fcf.normalizeObjectAddress(a_key);
          if (a_req[a_id + " -> " + a_key])
            return;
          a_req[a_id + " -> " + a_key] = true;

          let argInfo = clGetArgInfo(a_id, a_key);

          let duplicate = false;
          if ((a_id + argInfo.arg) in a_protectDublicattes) {
            let oldSuffixLength = fcf.parseObjectAddress(a_protectDublicattes[a_id + argInfo.arg]).length;
            let newSuffixLength = fcf.parseObjectAddress(argInfo.suffix).length;
            if (newSuffixLength >= oldSuffixLength)
              duplicate = true;
          }

          if (!duplicate) {
            a_protectDublicattes[a_id + argInfo.arg] = argInfo.suffix;
            let exinfo = {};
            let callInfo = {
              id:  a_id,
              arg: argInfo.arg,
              suffix: argInfo.suffix,
              value: fcf.application.getLocalData().getItem(a_id, argInfo.arg, true, exinfo),
            };
            a_eventMap[a_id + "->"+ a_key] = callInfo;
            if(!a_ignoreURLArgs && fcf.isArg(argInfo.source) && argInfo.source.type == "url"){
              fcf.application.setUrlArg(argInfo.source.arg, callInfo.value, fcf.getWrapper(callInfo.id));
            }
          }

          //going down on graph
          if (stReferences[a_id] && stReferences[a_id][argInfo.arg]){
            let refsInfo = stReferences[a_id][argInfo.arg];
            for(let childId in refsInfo) {
              for(let childArgPath in refsInfo[childId]) {
                let refChlidsPath = refsInfo[childId][childArgPath];
                if (argInfo.path.indexOf(refChlidsPath) == 0 || refChlidsPath.indexOf(argInfo.path) == 0){
                  let child = fcf.application.getLocalData().getSourceItem(childId, childArgPath);
                  if (!child)
                    continue;
                  let childArg = fcf.tokenize(child.arg, {args: fcf.application.getLocalData().getObject(a_id), route: fcf.getContext().route})
                  let childParts = fcf.parseObjectAddress(childArg);
                  childParts.shift()
                  let ownerParts = fcf.parseObjectAddress(argInfo.suffix);
                  let newSuffixParts = [];
                  let fullCopy = true;
                  for(let i = 0; i < ownerParts.length; ++i){
                    if (i >= childParts.length)
                      newSuffixParts.push(ownerParts[i])
                  }
                  let newSuffix = "";
                  for(var i = 0; i < newSuffixParts.length; ++i)
                    newSuffix += "[\"" + newSuffixParts[i] + "\"]";
                  let value = fcf.application.getLocalData().getItem(childId, childArgPath + newSuffix);
                  clProcessLink(childId,  childArgPath + newSuffix, value, a_req, a_eventMap, a_protectDublicattes, false, false);
                }
              }
            }
          }

          if (argInfo.source === undefined)
            return;

          //going up on graph
          if (fcf.isArg(argInfo.source) && argInfo.source.type == "reference"){
            clProcessLink(argInfo.source.object.id, argInfo.source.arg + argInfo.suffix, a_value, a_req, a_eventMap, a_protectDublicattes, false, true);
          }
        }

        try {
          if (!(a_callPosition in stCallsRecursion)) {
            stCallsRecursion[a_callPosition] = true;
            ++stArgRecursion;
            clProcessLink(this._id, a_key, a_value, {}, eventMap, {}, true, true);
            a_key = fcf.normalizeObjectAddress(a_key);
            for(var i in eventMap) {
              let id = eventMap[i].id;
              let eventInfo = eventMap[i];
              let wrp = fcf.getWrapper(id);
              if (!wrp || wrp._destroy){
                continue;
              }
              try {
                wrp._onArg(eventInfo.arg, eventInfo.value, a_editor, a_ignoreRedrawing, a_innerCall && wrp == a_editor, eventInfo.suffix);
                if (wrp.onArg){
                  wrp.onArg(eventInfo.arg, eventInfo.value, a_editor, a_ignoreRedrawing, a_innerCall && wrp == a_editor, eventInfo.suffix);
                }
              } catch(e){
                --this._callPositionsCounter;
                if (!this._callPositionsCounter)
                  this._callPositionsSetArg = {};
                fcf.log.err("APPLICATION:Wrapper", e);
                throw e;
              }

              let method = "onArg" + eventInfo.arg.charAt(0).toUpperCase() + eventInfo.arg.slice(1);
              try {
                if (wrp[method])
                  wrp[method](eventInfo.value, a_editor, a_ignoreRedrawing, a_innerCall && wrp == a_editor, eventInfo.suffix);
              } catch(e) {
                --this._callPositionsCounter;
                if (!this._callPositionsCounter)
                  this._callPositionsSetArg = {};
                fcf.log.err("APPLICATION:Wrapper", e);
                throw e;
              }
            }

            --stArgRecursion;
            if (stArgRecursion == 0 ) {
              stCallsRecursion = {};
            }
          }
        } catch (e) {
          fcf.application.getLocalData().setItem(this._id, a_key, originValue);
          --this._callPositionsCounter;
          this._callPositionsSetArg = {}
          stArgRecursion = 0;
          stCallsRecursion = {};
          throw e;
        }

        --this._callPositionsCounter;
        if (!this._callPositionsCounter)
          this._callPositionsSetArg = {};
      }


    };

    Wrapper.prototype._attachInnerEvents = function(){
      let self = this;
      // attach events by attributes
      function clAttach(a_element){
        if (!a_element)
          return;

        let fcfEvents = {};
        for(let i = 0; i < a_element.attributes.length; ++i){
          if(a_element.attributes[i].name.toLowerCase().indexOf("fcfevent") == 0) {
            fcfEvents[a_element.attributes[i].name.substr(8)] = a_element.attributes[i].value;
          }
        };

        for(let eventName in fcfEvents) {
          let eventCode = fcfEvents[eventName];
          let options = {
            wrapper: self,
            parent: self.getParent(),
          };
          (function(options, eventCode){
            fcf.addDomListener(a_element, eventName, self, function(event){
              return fcf.scriptExecutor.execute(eventCode, options, "&event&" + eventCode);
            });
          })(options, eventCode)
        }
        if (a_element.children) {
          for(var i = 0; i < a_element.children.length; ++i){
            if (a_element.children[i].getAttribute("fcftemplate"))
              continue;
            var r = clAttach(a_element.children[i]);
          }
        }
      }

      clAttach(this.getDomElement());

      // attach events by arguments
      var templateData = this.getShortArgs();
      for(var key in templateData){
        if (typeof key !== "string")
          continue;
        if (key.indexOf("fcfEvent") !== 0)
          continue;

        var eventName = key.substr(8);
            eventName = eventName[0].toLowerCase() + eventName.substr(1);
        var eventCode = templateData[key];
        var options = {
          wrapper: self,
          parent: self.getParent(),
        };
        (function(eventName, eventCode, options){
          fcf.addDomListener(self.getActionDomElement(), eventName, self, function(event){
            with(options){
              return eval(eventCode);
            }
          })
        })(eventName, eventCode, options);
      }

    }


    NClient.Wrapper = Wrapper;
    return NClient.Wrapper;

  }
});
