frontend CHANGE refactor data modification

move data manipulation operations into a separate service to allow
better splitting to components without code/templates duplication
and communication between components via events
diff --git a/frontend/config/config.component.html b/frontend/config/config.component.html
index f9a7d95..003bf94 100644
--- a/frontend/config/config.component.html
+++ b/frontend/config/config.component.html
@@ -71,8 +71,8 @@
     </div>
     <ng-container *ngIf="activeSession.dataVisibility!='none' && activeSession.data">
     <div id="config-data">
-        <tree-view [node]="root" [root]="root"></tree-view>
-        <!--  <pre *ngIf="root['schemaChildren']">{{root['schemaChildren'] | json}}</pre> -->
+        <tree-view [node]="activeSession.data"></tree-view>
+        <!--  <pre *ngIf="activeSession.data['schemaChildren']">{{activeSession.data['schemaChildren'] | json}}</pre> -->
     </div>
 	<div #modificationsStatus *ngIf="activeSession.modifications" class="modifications-status">&nbsp;
 		<div *ngIf="activeSession.modifications" class="msg-rounded" [style.width.px]="modificationsStatus.offsetWidth">
diff --git a/frontend/config/config.component.ts b/frontend/config/config.component.ts
index c916348..af12bff 100644
--- a/frontend/config/config.component.ts
+++ b/frontend/config/config.component.ts
@@ -1,6 +1,7 @@
 import {Component, OnInit} from '@angular/core';
 import {Router} from '@angular/router';
 
+import {ModificationsService} from './modifications.service';
 import {SessionsService} from './sessions.service';
 import {Session} from './session';
 
@@ -15,9 +16,10 @@
     activeSession: Session;
     err_msg = "";
     loading = false;
-    root: {};
 
-    constructor(private sessionsService: SessionsService, private router: Router) {}
+    constructor(private sessionsService: SessionsService,
+                private modsService: ModificationsService,
+                private router: Router) {}
 
     addSession() {
         this.router.navigateByUrl('/netopeer/inventory/devices');
@@ -141,14 +143,20 @@
         this.loading = true;
         this.sessionsService.rpcGetSubtree(this.activeSession.key, all).subscribe(result => {
             if (result['success']) {
-                this.activeSession.data = result['data'];
-                this.root['children'] = this.activeSession.data;
+                for (let iter of result['data']) {
+                    this.modsService.setDirty(this.activeSession, iter);
+                }
+                this.activeSession.data = {};
+                this.activeSession.data['path'] = '/';
+                this.activeSession.data['info'] = {};
+                this.activeSession.data['info']['path'] = '/';
+                this.activeSession.data['children'] = result['data'];
                 if (all) {
                     this.activeSession.dataVisibility = 'all';
                 } else {
                     this.activeSession.dataVisibility = 'root';
                 }
-                console.log(this.root);
+                console.log(this.activeSession.data);
             } else {
                 this.activeSession.dataVisibility = 'none';
                 if ('error-msg' in result) {
@@ -162,49 +170,9 @@
         });
     }
 
-    cancelChangesNode(node, recursion = true) {
-        if ('creatingChild' in node) {
-            delete node['creatingChild'];
-        }
-        if ('deleted' in node) {
-            node['dirty'] = false;
-            node['deleted'] = false;
-        }
-
-        if (this.activeSession.modifications) {
-            let record = this.sessionsService.getModificationsRecord(node['path']);
-            if (record) {
-                node['dirty'] = false;
-                if (record['type'] == 'change') {
-                    node['value'] = record['original'];
-                }
-                this.sessionsService.removeModificationsRecord(node['path']);
-                if (!this.activeSession.modifications) {
-                    return;
-                }
-            }
-        }
-
-        /* recursion */
-        if (recursion && 'children' in node) {
-            for (let child of node['children']) {
-                this.cancelChangesNode(child);
-            }
-            if ('newChildren' in node) {
-                for (let child of node['newChildren']) {
-                    this.sessionsService.removeModificationsRecord(child['path']);
-                }
-                delete node['newChildren'];
-                if (('children' in node) && node['children'].length) {
-                    node['children'][node['children'].length - 1]['last'] = true;
-                }
-            }
-        }
-    }
-
     cancelChanges() {
         //console.log(JSON.stringify(this.activeSession.modifications))
-        this.cancelChangesNode(this.root);
+        this.modsService.cancelModification(this.activeSession);
         this.sessionsService.storeData();
         //console.log(JSON.stringify(this.activeSession.modifications))
     }
@@ -217,11 +185,6 @@
     ngOnInit(): void {
         this.sessionsService.checkSessions();
         this.activeSession = this.sessionsService.getActiveSession();
-        this.root = {};
-        this.root['path'] = '/';
-        this.root['info'] = {};
-        this.root['info']['path'] = '/';
-        this.root['children'] = this.activeSession.data;
     }
 
     changeActiveSession(key: string) {
diff --git a/frontend/config/modifications.service.ts b/frontend/config/modifications.service.ts
new file mode 100644
index 0000000..a0de997
--- /dev/null
+++ b/frontend/config/modifications.service.ts
@@ -0,0 +1,471 @@
+import { Injectable } from '@angular/core';
+
+import { Session} from './session';
+import { SessionsService } from './sessions.service';
+
+@Injectable()
+export class ModificationsService{
+
+    constructor(private sessionsService: SessionsService) {}
+
+    createModificationsRecord(activeSession, path) {
+        if (!activeSession.modifications) {
+            activeSession.modifications = {};
+        }
+
+        if (!(path in activeSession.modifications)) {
+            activeSession.modifications[path] = {};
+        }
+        return activeSession.modifications[path];
+    }
+
+    getModificationsRecord(activeSession, path) {
+        if (!activeSession.modifications) {
+            return null;
+        }
+
+        if (!(path in activeSession.modifications)) {
+            return null;
+        }
+        return activeSession.modifications[path];
+    }
+
+    removeModificationsRecord(activeSession, path = null) {
+        if (!activeSession.modifications) {
+            return;
+        }
+
+        if (path && (path in activeSession.modifications)) {
+            delete activeSession.modifications[path];
+        }
+
+        if (!Object.keys(activeSession.modifications).length) {
+            delete activeSession.modifications;
+        }
+    }
+
+    setDirty(activeSession, node) {
+        if (!activeSession.modifications) {
+            return;
+        }
+
+        if (node['path'] in activeSession.modifications) {
+            node['dirty'] = true;
+            if (activeSession.modifications[node['path']]['type'] == 'change') {
+                activeSession.modifications[node['path']]['original'] = node['value'];
+            }
+            node['value'] = activeSession.modifications[node['path']]['value']; 
+        }
+        /* recursion */
+        if ('children' in node) {
+            for (let child of node['children']) {
+                this.setDirty(activeSession, child);
+            }
+        }
+    }
+
+    setEdit(node, set = true) {
+        if (set && node['info']['datatypebase'] == 'empty') {
+            node['value'] = '';
+            return;
+        }
+        node['edit'] = set;
+    }
+
+    setLast(list) {
+        let last;
+        for (let iter of list) {
+            delete iter['last'];
+            last = iter;
+        }
+        last['last'] = true;
+    }
+
+    nodeParent(activeSession, node) {
+        if (node['path'] =='/') {
+            return null;
+        }
+
+        let match = false;
+        let parent = null;
+        let children = activeSession.data['children'];
+        let newChildren;
+
+        while (children || newChildren) {
+            match = false;
+
+            if (children) {
+                for (let iter of children) {
+                    if (node['path'] == iter['path']) {
+                        match = true;
+                        children = null;
+                        newChildren = null;
+                        break;
+                    } else if (node['path'].startsWith(iter['path'] + '/')) {
+                        match = true;
+                        parent = iter;
+                        children = iter['children'];
+                        if (('new' in node) && ('newChildren' in iter)) {
+                            newChildren = iter['newChildren'];
+                        } else {
+                            newChildren = null;
+                        }
+                        break;
+                    }
+                }
+                if (!match) {
+                    children = null;
+                }
+            }
+            if (match) {
+                continue;
+            }
+            if (newChildren) {
+                for (let iter of newChildren) {
+                    if (node['path'] == iter['path']) {
+                        match = true;
+                        children = null;
+                        newChildren = null;
+                        break;
+                    } else if (node['path'].startsWith(iter['path'] + '/')) {
+                        match = true;
+                        parent = iter;
+                        children = iter['children'];
+                        if (('new' in node) && ('newChildren' in iter)) {
+                            newChildren = iter['newChildren'];
+                        } else {
+                            newChildren = null;
+                        }
+                        break;
+                    }
+                }
+                if (!match) {
+                    children = null;
+                }
+            }
+        }
+
+        if (!parent) {
+            parent = activeSession.data;
+        }
+        return parent;
+    }
+
+    schemaName(parent, child):string {
+        if (parent['module'] != child['module']) {
+            return child['module'] + ':' + child['name'];
+        } else {
+            return child['name'];
+        }
+    }
+
+    delete(activeSession, node) {
+        if ('new' in node) {
+            /* removing newly created subtree */
+            let parent = this.nodeParent(activeSession, node);
+            if ('new' in parent) {
+                /* removing just a subtree of the created tree */
+                for (let i in parent['children']) {
+                    if (parent['children'][i] == node) {
+                        parent['children'].splice(i, 1);
+                        break;
+                    }
+                }
+            } else {
+                this.removeModificationsRecord(activeSession, node['path']);
+                for (let i in parent['newChildren']) {
+                    if (parent['newChildren'][i]['path'] == node['path']) {
+                        parent['newChildren'].splice(i, 1);
+                        break;
+                    }
+                }
+                if (!parent['newChildren'].length) {
+                    delete parent['newChildren'];
+                }
+            }
+        } else {
+            let record = this.createModificationsRecord(activeSession, node['path']);
+
+            if (!('type' in record)) {
+                /* new record */
+                record['type'] = 'delete';
+                record['original'] = node;
+                node['deleted'] = true;
+                node['dirty'] = true;
+            } else if (record['type'] == 'change') {
+                record['type'] = 'delete';
+                node['value'] = record['original'];
+                delete record['original'];
+                delete record['value'];
+                node['deleted'] = true;
+            }
+        }
+    }
+
+    change(activeSession, node, leafValue) {
+        if (!('new' in node)) {
+            let record = this.createModificationsRecord(activeSession, node['path']);
+            if (!('type' in record)) {
+                console.log(record);
+                /* new record */
+                if (node['value'] == leafValue) {
+                    /* no change to the original value */
+                    this.setEdit(node, false);
+                    this.removeModificationsRecord(activeSession);
+                    return;
+                }
+                record['type'] = 'change';
+                record['original'] = node['value'];
+                record['value'] = leafValue;
+                node['dirty'] = true;
+            } else if (record['type'] == 'change' && record['original'] == leafValue) {
+                console.log(record);
+                /* change back to the original value, remove the change record */
+                this.removeModificationsRecord(activeSession, node['path']);
+                node['dirty'] = false;
+            } else {
+                console.log(record);
+                /* another change of existing change record */
+                record['value'] = leafValue;
+                node['dirty'] = true;
+            }
+        }
+
+        node['value'] = leafValue;
+        this.setEdit(node, false);
+    }
+
+    createOpen(schemas, node) {
+        //console.trace();
+        node['schemaChildren'] = schemas;
+        node['creatingChild'] = {};
+
+        if (schemas.length) {
+            if (('newChildren' in node) && node['newChildren'].length) {
+                delete node['newChildren'][node['newChildren'].length - 1]['last']
+            } else if (('children' in node) && node['children'].length) {
+                delete node['children'][node['children'].length - 1]['last'];
+            }
+        }
+    }
+
+    createClose(node, reason='abort') {
+        //console.trace();
+        if (reason == 'abort' && node['schemaChildren'].length) {
+            if (('newChildren' in node) && node['newChildren'].length) {
+                node['newChildren'][node['newChildren'].length - 1]['last'] = true;
+            } else if (('children' in node) && node['children'].length) {
+                node['children'][node['children'].length - 1]['last'] = true;
+            }
+        }
+        delete node['creatingChild'];
+        delete node['schemaChildren'];
+    }
+
+    create(activeSession, node, index) {
+        //console.trace();
+        let newNode = {};
+        newNode['new'] = true;
+        newNode['info'] = node['schemaChildren'][index];
+        if (node['path'] == '/') {
+            newNode['path'] = '/' + this.schemaName(node['info'], newNode['info']);
+        } else {
+            newNode['path'] = node['path'] + '/' + this.schemaName(node['info'], newNode['info']);
+        }
+        newNode['dirty'] = true;
+
+        if ('new' in node) {
+            if (!('children' in node)) {
+                node['children'] = []
+            }
+            node['children'].push(newNode)
+        } else {
+            if (!('newChildren' in node)) {
+                node['newChildren'] = [];
+            }
+            node['newChildren'].push(newNode);
+        }
+
+        switch(newNode['info']['type']) {
+        case 1: { /* container */
+            node['schemaChildren'].splice(index, 1);
+
+            newNode['children'] = [];
+            /* open creation dialog for nodes inside the created container */
+            this.sessionsService.childrenSchemas(activeSession.key, newNode['info']['path'], newNode).then(result => {
+                this.createOpen(result, newNode);
+            });
+            break;
+        }
+        case 4: { /* leaf */
+            node['schemaChildren'].splice(index, 1);
+
+            newNode['value'] = newNode['info']['default'];
+            this.setEdit(newNode, true)
+            break;
+        }
+        case 16: { /* list */
+            let search;
+            if ('new' in node) {
+                search = node['children'];
+            } else {
+                search = node['newChildren'];
+            }
+            let pos = 1;
+            if (search.length) {
+                for (let sibling of search) {
+                    if (sibling['path'].substr(0, newNode['path'].length + 1) == newNode['path'] + '[') {
+                        let n = parseInt(sibling['path'].substring(newNode['path'].length + 1));
+                        if (n >= pos) {
+                            pos = n + 1;
+                        }
+                    }
+                }
+            }
+            newNode['path'] = newNode['path'] + '[' + pos + ']';
+
+            newNode['children'] = [];
+            /* open creation dialog for nodes inside the created list */
+            this.sessionsService.childrenSchemas(activeSession.key, newNode['info']['path'], newNode).then(result => {
+                this.createOpen(result, newNode);
+
+                if (newNode['schemaChildren'].length) {
+                    for (let i in newNode['schemaChildren']) {
+                        if (!newNode['schemaChildren'][i]['key']) {
+                            continue;
+                        }
+                        let newKey = {};
+                        newKey['new'] = true;
+                        newKey['info'] = newNode['schemaChildren'][i];
+                        newKey['path'] = newNode['path'] + '/' + this.schemaName(newNode['info'], newKey['info']);
+                        newKey['dirty'] = true;
+                        this.setEdit(newKey, true)
+                        newNode['children'].push(newKey)
+                        newNode['schemaChildren'].splice(i, 1);
+                    }
+                }
+            });
+
+            break;
+        }
+        }
+
+        if (!node['schemaChildren'].length) {
+            newNode['last'] = true;
+            this.createClose(node, 'success');
+        }
+
+        if (!('new' in node)) {
+            /* store the record about the newly created data */
+            let record = this.createModificationsRecord(activeSession, newNode['path']);
+            if (('type' in record) && record['type'] == 'delete') {
+                record['type'] = 'change';
+                record['value'] = newNode;
+                delete record['original']['deleted'];
+                for (let i in node['children']) {
+                    if (node['children'][i] == record['original']) {
+                        node['children'].splice(i, 1);
+                        break;
+                    }
+                }
+            } else {
+                record['type'] = 'create';
+                record['data'] = newNode;
+            }
+        }
+        console.log(node)
+    }
+
+    cancelModification(activeSession, node = activeSession.data, recursion = true) {
+        if ('creatingChild' in node) {
+            delete node['creatingChild'];
+        }
+        if ('deleted' in node) {
+            node['dirty'] = false;
+            node['deleted'] = false;
+        }
+
+        if ('new' in node) {
+            /* removing newly created subtree */
+            let parent = this.nodeParent(activeSession, node);
+            if ('new' in parent) {
+                /* removing just a subtree of the created tree */
+                for (let i in parent['children']) {
+                    if (parent['children'][i] == node) {
+                        if (Number(i) > 0 && parent['children'][i]['last']) {
+                            parent['children'][Number(i) - 1]['last'] = true;
+                        }
+                        parent['children'].splice(i, 1);
+                        break;
+                    }
+                }
+            } else {
+                this.removeModificationsRecord(activeSession, node['path']);
+                for (let i in parent['newChildren']) {
+                    if (parent['newChildren'][i]['path'] == node['path']) {
+                        if (Number(i) > 0 && parent['newChildren'][i]['last']) {
+                            parent['newChildren'][Number(i) - 1]['last'] = true;
+                        }
+                        parent['newChildren'].splice(i, 1);
+                        break;
+                    }
+                }
+                if (!parent['newChildren'].length) {
+                    delete parent['newChildren'];
+                }
+            }
+
+            if (node['info']['type'] == 1 || node['info']['type'] == 4) {
+                /* fix the list of nodes to create in parent */
+                let schemas;
+                if (!('schemaChildren' in parent)) {
+                    schemas = [];
+                } else {
+                    schemas = parent['schemaChildren'];
+                }
+                schemas.push(node['info']);
+                this.createOpen(schemas, parent)
+            }
+        } else if (activeSession.modifications) {
+            let record = this.getModificationsRecord(activeSession, node['path']);
+            if (record) {
+                node['dirty'] = false;
+                if (record['type'] == 'change') {
+                    node['value'] = record['original'];
+                }
+                this.removeModificationsRecord(activeSession, node['path']);
+                if (!activeSession.modifications) {
+                    return;
+                }
+            }
+        }
+
+        /* recursion */
+        if (recursion && 'children' in node) {
+            if ('newChildren' in node) {
+                for (let child of node['newChildren']) {
+                    let record = this.getModificationsRecord(activeSession, child['path']);
+                    if (record['type'] == 'change') {
+                        if (node['children'].length) {
+                            node['children'][node['children'].length - 1]['last'] = false;
+                        }
+                        record['original']['last'] = true;
+                        node['children'].push(record['original'])
+                    }
+                    this.removeModificationsRecord(activeSession, child['path']);
+                }
+                delete node['newChildren'];
+                if (('children' in node) && node['children'].length) {
+                    node['children'][node['children'].length - 1]['last'] = true;
+                }
+            }
+            let last;
+            for (let child of node['children']) {
+                this.cancelModification(activeSession, child);
+                delete child['last'];
+                last = child;
+            }
+            last['last'] = true;
+        }
+    }
+}
diff --git a/frontend/config/sessions.service.ts b/frontend/config/sessions.service.ts
index 68fa136..3d3549b 100644
--- a/frontend/config/sessions.service.ts
+++ b/frontend/config/sessions.service.ts
@@ -81,45 +81,6 @@
         }
     }
 
-    createModificationsRecord(path) {
-        let activeSession = this.getActiveSession();
-        if (!activeSession.modifications) {
-            activeSession.modifications = {};
-        }
-
-        if (!(path in activeSession.modifications)) {
-            activeSession.modifications[path] = {};
-        }
-        return activeSession.modifications[path];
-    }
-
-    getModificationsRecord(path) {
-        let activeSession = this.getActiveSession();
-        if (!activeSession.modifications) {
-            return null;
-        }
-
-        if (!(path in activeSession.modifications)) {
-            return null;
-        }
-        return activeSession.modifications[path];
-    }
-
-    removeModificationsRecord(path = null) {
-        let activeSession = this.getActiveSession();
-        if (!activeSession.modifications) {
-            return;
-        }
-
-        if (path && (path in activeSession.modifications)) {
-            delete activeSession.modifications[path];
-        }
-
-        if (!Object.keys(activeSession.modifications).length) {
-            delete activeSession.modifications;
-        }
-    }
-
     checkValue(key: string, path: string, value: string): Observable<string[]> {
         let params = new URLSearchParams();
         params.set('key', key);
@@ -216,19 +177,7 @@
         return this.http.get('/netopeer/session/rpcGet', options)
             .map((resp: Response) => {
                 //console.log(resp);
-                let result = resp.json();
-                if (result['success']) {
-                    if (path.length) {
-                        for (let iter of result['data']['children']) {
-                            this.setDirty(iter);
-                        }
-                    } else {
-                        for (let iter of result['data']) {
-                            this.setDirty(iter);
-                        }
-                    }
-                }
-                return result;
+                return resp.json();
             })
             .catch((err: Response | any) => Observable.throw(err));
     }
diff --git a/frontend/config/tree-create.html b/frontend/config/tree-create.html
new file mode 100644
index 0000000..26a6ef3
--- /dev/null
+++ b/frontend/config/tree-create.html
@@ -0,0 +1,20 @@
+<!-- create new child dialog -->
+<div *ngIf="node['creatingChild']" id="{{node['path']}}_createChildDialog"
+    class="node_edit" [class.dialog]="node['schemaChildren']" 
+    treeScrollTo [node]="node">
+    <ng-container *ngIf="node['schemaChildren'].length; else nothingToCreate">
+        <tree-indent [node]="node" [indentation]="indentation" [type]="'edit'"></tree-indent>
+        <select (change)="creatingDialogSelect(node, $event.target.value, $event.currentTarget)">
+            <option disabled selected value="-1" style="display: none">&nbsp;</option>
+            <ng-container *ngFor="let schema of node['schemaChildren']; let i='index'">
+                <option value="{{i}}">{{modsService.schemaName(node['info'], schema)}}</option>
+            </ng-container>
+        </select>
+        <!--  <pre>{{node['schemaChildren'] | json}}</pre> -->
+    </ng-container>
+    <ng-template #nothingToCreate>
+        <div class="msg-failure msg-rounded">
+            <span class="msg-close" (click)="closeCreatingDialog(node);">x</span>There is no element to create at {{node['path']}}.
+        </div>
+    </ng-template>
+</div>
\ No newline at end of file
diff --git a/frontend/config/tree-indent.html b/frontend/config/tree-indent.html
index 9a29285..2d3fd82 100644
--- a/frontend/config/tree-indent.html
+++ b/frontend/config/tree-indent.html
@@ -3,15 +3,11 @@
         onmouseover="this.src='assets/netopeer/icons/close_active.svg'"
         onmouseout="this.src='assets/netopeer/icons/close.svg'"
         (click)="closeCreatingDialog(node)"/>
-    <img *ngIf="node['info']['type'] == 4 && (type == 'edit' || node['key'])" class="icon_action" src="assets/netopeer/icons/tree_empty.svg"/>
-    <img *ngIf="type != 'edit' && node['dirty'] && !node['deleted'] && !node['key']" class="icon_action" src="assets/netopeer/icons/close.svg"
+    <img *ngIf="node['info']['type'] == 4 && (type == 'edit' || node['info']['key'])" class="icon_action" src="assets/netopeer/icons/tree_empty.svg"/>
+    <img *ngIf="type != 'edit' && node['dirty'] && !node['info']['key']" class="icon_action" src="assets/netopeer/icons/close.svg"
         onmouseover="this.src='assets/netopeer/icons/close_active.svg'"
         onmouseout="this.src='assets/netopeer/icons/close.svg'"
-        (click)="deleteSubtree(node)"/>
-    <img *ngIf="node['deleted']" class="icon_action" src="assets/netopeer/icons/close.svg"
-        onmouseover="this.src='assets/netopeer/icons/close_active.svg'"
-        onmouseout="this.src='assets/netopeer/icons/close.svg'"
-        (click)="cancelDelete(node)"/>
+        (click)="cancelModification(node)"/>
 </ng-container>
 <ng-template #menu >
     <span *ngIf="type == 'root' || node['info']['config']; else empty"
@@ -21,7 +17,7 @@
             onmouseover="this.src='assets/netopeer/icons/menu_active.svg'"
             onmouseout="this.src='assets/netopeer/icons/menu.svg'"/>
         <div class="editmenu msg-rounded">
-            <div *ngIf="type != 'root'" class="button_action" title="remove subtree {{node['path']}}"
+            <div *ngIf="type != 'root' && !node['info']['key']" class="button_action" title="remove subtree {{node['path']}}"
                 onmouseover="this.firstElementChild.src='assets/netopeer/icons/close_active.svg'"
                 onmouseout="this.firstElementChild.src='assets/netopeer/icons/close.svg'"
                 (click)="deleteSubtree(node);hideEditMenu($event.currentTarget.parentElement);">
diff --git a/frontend/config/tree.component.html b/frontend/config/tree.component.html
index 89c023b..1c42f86 100644
--- a/frontend/config/tree.component.html
+++ b/frontend/config/tree.component.html
@@ -1,47 +1,25 @@
 <div *ngIf="node['path'] == '/'">
     <!-- recursion - show children -->
     <div class="node">
-        <tree-indent [node]="node" [indentation]="indentation" [type]="'root'"
-            (onOpenCreatingDialog)="openCreatingDialog($event.element, $event.node, $event.parent)">
-        </tree-indent>
+        <tree-indent [node]="node" [indentation]="indentation" [type]="'root'"></tree-indent>
     </div>
     <div class="children" *ngIf="node['children'] || node['newChildren']">
         <ng-container *ngFor="let child of node['children']">
-            <tree-view [node]="child" [root]="root"></tree-view>
+            <tree-view [node]="child"></tree-view>
         </ng-container>
         <ng-container *ngFor="let child of newChildrenToShow(node)">
-            <tree-view [node]="child" [root]="root"></tree-view>
+            <tree-view [node]="child"></tree-view>
         </ng-container>
     </div>
 
     <!-- create new child dialog -->
-    <div id="{{node['path']}}_createChildDialog" class="node_edit" *ngIf="node['creatingChild']" [class.dialog]="node['schemaChildren']"
-        treeScrollTo [node]="node">
-        <ng-container *ngIf="node['schemaChildren'].length; else nothingToCreate">
-            <tree-indent [node]="node" [indentation]="indentation" [type]="'edit'"
-                (onCloseCreatingDialog)="closeCreatingDialog($event)"></tree-indent>
-            <select #nodeSelect (change)="creatingDialogSelect(node, $event.target.value, $event.currentTarget)">
-                <option disabled selected value="-1" style="display:none">&nbsp;</option>
-                <ng-container *ngFor="let schema of node['schemaChildren']; let i='index'">
-                    <option value="{{i}}">{{schemaInfoName(node, schema)}}</option>
-                </ng-container>
-            </select>
-            <!--  <pre>{{node['schemaChildren'] | json}}</pre> -->
-        </ng-container>
-        <ng-template #nothingToCreate>
-            <div class="msg-failure msg-rounded">
-                <span class="msg-close" (click)="closeCreatingDialog(node);">x</span>There is no element to create at {{node['path']}}.
-            </div>
-        </ng-template>
-    </div>
+    <tree-create [node]="node" [indentation]="indentation"></tree-create>
 </div>
 <div class="subtree" *ngIf="node['path'] != '/' && (node['info']['config'] || activeSession.statusVisibility)"
     [class.status]="!node['info']['config']">
 
 	<div class="node" [class.dirty]="node['dirty']" [class.deleted]="node['deleted']">
-		<tree-indent [node]="node" [indentation]="indentation"
-			(onDeleteSubtree)="deleteSubtree($event)" (onOpenCreatingDialog)="openCreatingDialog($event.element, $event.node, $event.parent)">
-		</tree-indent>
+		<tree-indent [node]="node" [indentation]="indentation"></tree-indent>
 		<img class="icon" src="assets/netopeer/icons/info.svg" alt="info" title="{{node['info']['dsc']}}"
 			onmouseover="this.src='assets/netopeer/icons/info_active.svg'"
 			onmouseout="this.src='assets/netopeer/icons/info.svg'"/>
@@ -52,7 +30,7 @@
 			<ng-container *ngIf="node['info']['key']">
 				<img class="icon" src="assets/netopeer/icons/key.svg" alt="key" title="list key"/>
 			</ng-container>
-			<ng-container *ngIf="node['info']['config'] && !node['info']['key'] && node['info']['datatypebase'] != 'empty'">
+			<ng-container *ngIf="node['info']['config'] && !node['deleted'] && (!node['info']['key'] || node['new']) && node['info']['datatypebase'] != 'empty'">
 				<img *ngIf="!node['edit']" class="icon_action" src="assets/netopeer/icons/edit.svg"
 					alt="edit" title="edit value" tabindex=0
 					(click)="startEditing(node, $event.target);" (keyup.enter)="startEditing(node, $event.target);"
@@ -102,9 +80,7 @@
 	<!-- leaf's edit value -->
 	<ng-container *ngIf="node['info']['type'] == 4">
 		<div class="node_edit" [class.dirty]="node['dirty']" *ngIf="node['edit']">
-            <tree-indent [node]="node" [indentation]="inheritIndentation(node)" [type]="'edit'"
-                (onCloseCreatingDialog)="closeCreatingDialog($event)"
-                (onDeleteSubtree)="deleteSubtree($event)"></tree-indent>
+            <tree-indent [node]="node" [indentation]="inheritIndentation(node)" [type]="'edit'"></tree-indent>
 			<img *ngIf="node['edit'] " class="icon_action" src="assets/netopeer/icons/close.svg"
 				alt="cancel" title="cancel editing" tabindex=0
 				(click)="node['edit']=false" (keyup.enter)="node['edit']=false"
@@ -137,32 +113,13 @@
     </div>
     <div class="children" *ngIf="(node['children'] || node['newChildren']) && !node['deleted']">
         <ng-container *ngFor="let child of node['children']">
-            <tree-view [node]="child" [root]="root" [indentation]="inheritIndentation(node)"></tree-view>
+            <tree-view [node]="child" [indentation]="inheritIndentation(node)"></tree-view>
         </ng-container>
         <ng-container *ngFor="let child of newChildrenToShow(node)">
-            <tree-view [node]="child" [root]="root" [indentation]="inheritIndentation(node)"></tree-view>
+            <tree-view [node]="child" [indentation]="inheritIndentation(node)"></tree-view>
         </ng-container>
     </div>
 
     <!-- create new child dialog -->
-	<div id="{{node['path']}}_createChildDialog" class="node_edit" *ngIf="node['creatingChild']" [class.dialog]="node['schemaChildren']"
-        treeScrollTo [node]="node">
-		<ng-container *ngIf="node['schemaChildren'].length; else nothingToCreate">
-            <tree-indent [node]="node" [indentation]="inheritIndentation(node)" [type]="'edit'"
-                (onCloseCreatingDialog)="closeCreatingDialog($event)"></tree-indent>
-			<select #nodeSelect (change)="creatingDialogSelect(node, $event.target.value, $event.currentTarget)">
-				<option disabled selected value="-1" style="display:none">&nbsp;</option>
-				<ng-container *ngFor="let schema of node['schemaChildren']; let i='index'">
-					<option value="{{i}}">{{schemaInfoName(node, schema)}}</option>
-				</ng-container>
-			</select>
-            <!--  <pre>{{node['schemaChildren'] | json}}</pre> -->
-		</ng-container>
-		<ng-template #nothingToCreate>
-			<div class="msg-failure msg-rounded">
-				<span class="msg-close" (click)="closeCreatingDialog(node);">x</span>There is no element to create at {{node['path']}}.
-			</div>
-		</ng-template>
-	</div>
-
+    <tree-create [node]="node" [indentation]="inheritIndentation(node)"></tree-create>
 </div>
diff --git a/frontend/config/tree.component.scss b/frontend/config/tree.component.scss
index 6df3b8e..29571be 100644
--- a/frontend/config/tree.component.scss
+++ b/frontend/config/tree.component.scss
@@ -52,10 +52,7 @@
     }
 }
 .node_edit.dialog {
-    background-color: $colorChanged;
-    border: 2px solid $colorChangedBorder;
-    border-left-width: 0px;
-    border-right-width: 0px;
+    background-color: $colorChanged;;
 }
 
 .status,
diff --git a/frontend/config/tree.component.ts b/frontend/config/tree.component.ts
index 3cde0a1..5c79843 100644
--- a/frontend/config/tree.component.ts
+++ b/frontend/config/tree.component.ts
@@ -2,6 +2,7 @@
 import {Router} from '@angular/router';
 
 import {Session} from './session';
+import {ModificationsService} from './modifications.service';
 import {SessionsService} from './sessions.service';
 
 @Directive({
@@ -27,6 +28,7 @@
     @Output() onCheckValue = new EventEmitter();
 
     constructor(private elRef:ElementRef) {}
+
     ngAfterContentInit() {
         console.log(this.node)
         let node = this.node;
@@ -37,6 +39,35 @@
 }
 
 @Component({
+    selector: 'tree-create',
+    templateUrl: 'tree-create.html',
+    styleUrls: ['./tree.component.scss']
+})
+export class TreeCreate implements OnInit {
+    @Input() node;
+    @Input() indentation;
+    activeSession: Session;
+
+    constructor(private modsService: ModificationsService, private sessionsService: SessionsService) {}
+
+    ngOnInit(): void {
+        this.activeSession = this.sessionsService.getActiveSession();
+    }
+
+    closeCreatingDialog(node, reason='abort') {
+        this.modsService.createClose(node, reason);
+    }
+
+    creatingDialogSelect(node, index, source) {
+        this.modsService.create(this.activeSession, node, index);
+        this.sessionsService.storeData();
+        if (node['schemaChildren'].length) {
+            source.selectedIndex = 0;
+        }
+    }
+}
+
+@Component({
     selector: 'tree-indent',
     templateUrl: 'tree-indent.html',
     styleUrls: ['./tree.component.scss']
@@ -45,14 +76,10 @@
     @Input() node;
     @Input() indentation;
     @Input() type = "current";
-    @Output() onShowEditMenu = new EventEmitter();
-    @Output() onDeleteSubtree = new EventEmitter();
-    @Output() onOpenCreatingDialog = new EventEmitter();
-    @Output() onCloseCreatingDialog = new EventEmitter();
     activeSession: Session;
     timeout;
 
-    constructor(private sessionsService: SessionsService) {}
+    constructor(private modsService: ModificationsService, private sessionsService: SessionsService) {}
 
     ngOnInit(): void {
         this.activeSession = this.sessionsService.getActiveSession();
@@ -73,19 +100,31 @@
     }
 
     deleteSubtree(node) {
-        this.onDeleteSubtree.emit(node);
-    }
-    openCreatingDialog(element, node, parent) {
-        this.onOpenCreatingDialog.emit({element, node, parent});
-    }
-    closeCreatingDialog(node) {
-        this.onCloseCreatingDialog.emit(node);
+        this.modsService.delete(this.activeSession, node);
+        this.sessionsService.storeData();
     }
 
-    cancelDelete(node) {
-        node['dirty'] = false;
-        node['deleted'] = false;
-        this.sessionsService.removeModificationsRecord(node['path']);
+    openCreatingDialog(element, node, parent = false) {
+        if (parent) {
+            node = this.modsService.nodeParent(this.activeSession, node);
+        }
+        if (!('creatingChild' in node)) {
+            this.sessionsService.childrenSchemas(this.activeSession.key, node['info']['path'], node).then(result => {
+                this.modsService.createOpen(result, node);
+            });
+        } else if (element){
+            /* scroll to the existing element */
+            element.ownerDocument.getElementById(node['path'] + '_createChildDialog').scrollIntoView(false);
+        }
+    }
+
+    closeCreatingDialog(node, reason='abort') {
+        this.modsService.createClose(node, reason);
+    }
+
+    cancelModification(node) {
+        this.modsService.cancelModification(this.activeSession, node, false);
+        this.sessionsService.storeData();
     }
 }
 
@@ -97,10 +136,11 @@
 
 export class TreeView implements OnInit {
     @Input() node;
-    @Input() root;
     @Input() indentation;
     activeSession: Session;
-    constructor(private sessionsService: SessionsService, private changeDetector: ChangeDetectorRef) {}
+    constructor(private modsService: ModificationsService,
+                private sessionsService: SessionsService,
+                private changeDetector: ChangeDetectorRef) {}
 
     ngOnInit(): void {
         this.activeSession = this.sessionsService.getActiveSession();
@@ -122,12 +162,12 @@
     }
 
     startEditing(node, target) {
-        if (node['info']['key'] && !node['new']) {
+        if ((node['info']['key'] && !node['new']) || node['deleted']) {
             return;
         }
         let parent = target.parentElement;
 
-        this.setNodeEdit(node, true)
+        this.modsService.setEdit(node, true)
         this.changeDetector.detectChanges();
 
         parent.nextElementSibling.lastElementChild.focus();
@@ -161,7 +201,7 @@
 
     changeValueCancel(node) {
         if ('value' in node) {
-            node['edit'] = false;
+            this.modsService.setEdit(node, false);
         }
     }
 
@@ -176,318 +216,10 @@
             input = target.nextElementSibling;
         }
 
-        if (!('new' in node)) {
-            let record = this.sessionsService.createModificationsRecord(node['path']);
-            if (!('type' in record)) {
-                console.log(record);
-                /* new record */
-                if (node['value'] == input['value']) {
-                    /* no change to the original value */
-                    this.setNodeEdit(node, false);
-                    this.sessionsService.removeModificationsRecord();
-                    return;
-                }
-                record['type'] = 'change';
-                record['original'] = node['value'];
-                record['value'] = input.value;
-                node['dirty'] = true;
-            } else if (record['type'] == 'change' && record['original'] == input['value']) {
-                console.log(record);
-                /* change to the original value, remove the change record */
-                this.sessionsService.removeModificationsRecord(node['path']);
-                node['dirty'] = false;
-            } else {
-                console.log(record);
-                /* another change of existing change record */
-                record['value'] = input.value;
-                node['dirty'] = true;
-            }
-            console.log(this.activeSession.modifications);
-        }
-
-        node['value'] = input.value;
-        this.setNodeEdit(node, false);
-        console.log(this.activeSession.data);
+        this.modsService.change(this.activeSession, node, input.value);
         this.sessionsService.storeData();
     }
 
-    deleteSubtree(node) {
-        if ('new' in node) {
-            /* removing newly created subtree */
-            let parent = this.nodeParent(node);
-            if ('new' in parent) {
-                /* removing just a subtree of the created tree */
-                for (let i in parent['children']) {
-                    if (parent['children'][i] == node) {
-                        parent['children'].splice(i, 1);
-                        break;
-                    }
-                }
-            } else {
-                this.sessionsService.removeModificationsRecord(node['path']);
-                for (let i in parent['newChildren']) {
-                    if (parent['newChildren'][i]['path'] == node['path']) {
-                        parent['newChildren'].splice(i, 1);
-                        break;
-                    }
-                }
-                if (!parent['newChildren'].length) {
-                    delete parent['newChildren'];
-                }
-            }
-        } else {
-            let record = this.sessionsService.createModificationsRecord(node['path']);
-
-            if (!('type' in record)) {
-                /* new record */
-                record['type'] = 'delete';
-                node['deleted'] = true;
-                node['dirty'] = true;
-            } else if (record['type'] == 'change') {
-                record['type'] = 'delete';
-                node['value'] = record['original'];
-                delete record['original'];
-                delete record['value'];
-                node['deleted'] = true;
-            }
-        }
-        console.log(this.activeSession.modifications);
-        this.sessionsService.storeData();
-    }
-
-    nodeParent(node) {
-        let match = false;
-        let parent = null;
-        let children = this.activeSession.data;
-        let newChildren;
-        while (children || newChildren) {
-            match = false;
-
-            if (children) {
-                for (let iter of children) {
-                    if (node['path'] == iter['path']) {
-                        match = true;
-                        children = null;
-                        newChildren = null;
-                        break;
-                    } else if (node['path'].startsWith(iter['path'] + '/')) {
-                        match = true;
-                        parent = iter;
-                        children = iter['children'];
-                        if (('new' in node) && ('newChildren' in iter)) {
-                            newChildren = iter['newChildren'];
-                        } else {
-                            newChildren = null;
-                        }
-                        break;
-                    }
-                }
-                if (!match) {
-                    children = null;
-                }
-            }
-            if (match) {
-                continue;
-            }
-            if (newChildren) {
-                for (let iter of newChildren) {
-                    if (node['path'] == iter['path']) {
-                        match = true;
-                        children = null;
-                        newChildren = null;
-                        break;
-                    } else if (node['path'].startsWith(iter['path'] + '/')) {
-                        match = true;
-                        parent = iter;
-                        children = iter['children'];
-                        if (('new' in node) && ('newChildren' in iter)) {
-                            newChildren = iter['newChildren'];
-                        } else {
-                            newChildren = null;
-                        }
-                        break;
-                    }
-                }
-                if (!match) {
-                    children = null;
-                }
-            }
-        }
-        if (!parent) {
-            parent = this.root;
-        }
-
-        return parent;
-    }
-
-    schemaInfoName(parentNode, childSchema):string {
-        if (parentNode['info']['module'] != childSchema['module']) {
-            return childSchema['module'] + ':' + childSchema['name'];
-        } else {
-            return childSchema['name'];
-        }
-    }
-
-    setNodeEdit(node, value) {
-        if (value && node['info']['datatypebase'] == 'empty') {
-            node['value'] = '';
-            return;
-        }
-        node['edit'] = value;
-    }
-
-    creatingDialogSelect(node, index, source) {
-        let newNode = {};
-        newNode['new'] = true;
-        newNode['info'] = node['schemaChildren'][index];
-        if (node['path'] == '/') {
-            newNode['path'] = '/' + this.schemaInfoName(node, newNode['info']);
-        } else {
-            newNode['path'] = node['path'] + '/' + this.schemaInfoName(node, newNode['info']);
-        }
-        newNode['dirty'] = true;
-
-        if ('new' in node) {
-            if (!('children' in node)) {
-                node['children'] = []
-            }
-            node['children'].push(newNode)
-        } else {
-            if (!('newChildren' in node)) {
-                node['newChildren'] = [];
-            }
-            node['newChildren'].push(newNode);
-        }
-
-        switch(newNode['info']['type']) {
-        case 1: { /* container */
-            newNode['children'] = [];
-            node['schemaChildren'].splice(index, 1);
-            this.openCreatingDialog(null, newNode);
-            break;
-        }
-        case 4: { /* leaf */
-            newNode['value'] = newNode['info']['default'];
-            this.setNodeEdit(newNode, true)
-            node['schemaChildren'].splice(index, 1);
-            break;
-        }
-        case 16: { /* list */
-            let search;
-            if ('new' in node) {
-                search = node['children'];
-            } else {
-                search = node['newChildren'];
-            }
-            let pos = 1;
-            if (search.length) {
-                for (let sibling of search) {
-                    console.log(sibling);
-                    console.log('testing ' + sibling['path'].substr(0, newNode['path'].length + 1));
-                    if (sibling['path'].substr(0, newNode['path'].length + 1) == newNode['path'] + '[') {
-                        console.log(sibling['path'].substring(newNode['path'].length + 1))
-                        let n = parseInt(sibling['path'].substring(newNode['path'].length + 1));
-                        console.log('found ' + n + ' in ' + sibling['path'])
-                        if (n >= pos) {
-                            pos = n + 1;
-                        }
-                    }
-                }
-            }
-            newNode['path'] = newNode['path'] + '[' + pos + ']';
-            console.log(newNode['path'])
-
-            newNode['children'] = [];
-            this.openCreatingDialog(null, newNode);
-
-            /* wait to receive schemaChildren of the list */
-            (function wait(context, flag: boolean) {
-                console.log(context)
-                setTimeout(() => {
-                    if ('schemaChildren' in newNode) {
-                        if (newNode['schemaChildren'].length) {
-                            console.log(newNode);
-                            for (let i in newNode['schemaChildren']) {
-                                if (!newNode['schemaChildren'][i]['key']) {
-                                    continue;
-                                }
-                                let newKey = {};
-                                newKey['new'] = true;
-                                newKey['key'] = true;
-                                newKey['info'] = newNode['schemaChildren'][i];
-                                newKey['path'] = newNode['path'] + '/' + context.schemaInfoName(newNode, newKey['info']);
-                                newKey['dirty'] = true;
-                                context.setNodeEdit(newKey, true)
-                                newNode['children'].push(newKey)
-                                newNode['schemaChildren'].splice(i, 1);
-                            }
-                        }
-                    } else {
-                        this.wait(context, true);
-                    }
-                }, 10);
-            })(this, true);
-            break;
-        }
-        }
-        if (!node['schemaChildren'].length) {
-            newNode['last'] = true;
-            this.closeCreatingDialog(node, 'success');
-        } else {
-            source.selectedIndex = 0;
-        }
-
-        if (!('new' in node)) {
-            let record = this.sessionsService.createModificationsRecord(newNode['path']);
-            record['type'] = 'create';
-            record['data'] = newNode;
-        }
-        console.log(newNode)
-    }
-
-    openCreatingDialog(element, node, parent = false) {
-        if (parent) {
-            node = this.nodeParent(node);
-        }
-        if (!('creatingChild' in node)) {
-            this.sessionsService.childrenSchemas(this.activeSession.key, node['info']['path'], node).then(result => {
-                console.log(result)
-                node['schemaChildren'] = result;
-                node['creatingChild'] = {};
-            });
-        } else if (element){
-            /* scroll to the existing element */
-            element.ownerDocument.getElementById(node['path'] + '_createChildDialog').scrollIntoView(false);
-        }
-        if (('children' in node) && node['children'].length) {
-            node['children'][node['children'].length - 1]['last'] = false;
-        }
-        console.log(node);
-    }
-
-    closeCreatingDialog(node, reason='abort') {
-        console.log(node)
-        if (reason == 'abort' && ('children' in node) && node['children'].length) {
-            node['children'][node['children'].length - 1]['last'] = true;
-        }
-        delete node['creatingChild'];
-        delete node['schemaChildren'];
-        if ('new' in node && !node['children'].length) {
-            let parent = this.nodeParent(node);
-            for (let i in parent['children']) {
-                if (parent['children'][i] == node) {
-                    if (!('schemaChildren' in parent)) {
-                        parent['schemaChildren'] = [];
-                        parent['creatingChild'] = {};
-                    }
-                    parent['schemaChildren'].push(node['info']);
-                    parent['children'].splice(i, 1);
-                    break;
-                }
-            }
-        }
-    }
-
     expandable(node): boolean {
         if (node['info']['type'] == 1 || /* container */
             node['info']['type'] == 16) { /* list */
@@ -532,6 +264,9 @@
         node['loading'] = true;
         this.sessionsService.rpcGetSubtree(this.activeSession.key, all, node['path']).subscribe(result => {
             if (result['success']) {
+                for (let iter of result['data']['children']) {
+                    this.modsService.setDirty(this.activeSession, iter);
+                }
                 node['children'] = result['data']['children'];
                 for (let iter of this.activeSession.data) {
                     this.hasHiddenChild(iter, true);
diff --git a/frontend/netopeer.module.ts b/frontend/netopeer.module.ts
index e030f7b..9c33886 100644
--- a/frontend/netopeer.module.ts
+++ b/frontend/netopeer.module.ts
@@ -20,12 +20,13 @@
 import { InventorySchemasComponent } from './inventory/schemas.component';
 import { InventoryDevicesComponent } from './inventory/devices.component';
 import { ConfigComponent } from './config/config.component';
-import { TreeView, TreeIndent, TreeScrollTo, CheckLeafValue } from './config/tree.component';
+import { TreeView, TreeIndent, TreeCreate, TreeScrollTo, CheckLeafValue } from './config/tree.component';
 import { YANGComponent } from './yang/yang.component';
 import { MonitoringComponent } from './monitoring/monitoring.component';
 import { PluginsComponent } from './plugins/plugins.component';
 
 import { SessionsService } from './config/sessions.service'
+import { ModificationsService } from './config/modifications.service'
 
 const routes: Routes = [
   { path : 'netopeer', component : NetopeerComponent, canActivate : [AuthGuard],
@@ -97,13 +98,15 @@
     CheckLeafValue,
     TreeScrollTo,
     TreeIndent,
+    TreeCreate,
     TreeView,
     YANGComponent,
     MonitoringComponent,
     PluginsComponent
   ],
   providers: [
-    SessionsService
+    SessionsService,
+    ModificationsService
   ],
   entryComponents : [
     NetopeerComponent