소스 검색

Stat tree edit (wip)

Vijayakrishnan 3 년 전
부모
커밋
fd8c5e6d82
2개의 변경된 파일265개의 추가작업 그리고 158개의 파일을 삭제
  1. 103 61
      app/Http/Controllers/StatTreeController.php
  2. 162 97
      resources/views/app/stat-tree/stat-trees/sub/edit.blade.php

+ 103 - 61
app/Http/Controllers/StatTreeController.php

@@ -236,12 +236,13 @@ class StatTreeController extends Controller
                 "li_attr" => [
                     "type" => "clause_text"
                 ],
-                "system" => [
+                "data" => [
                     "type" => "clause_text"
                 ]
             ];
 
             // clause arg children
+            $argsSystem = [];
             foreach ($clause->clauseArgs as $clauseArg) {
                 $children[] = [
                     "text" => $clauseArg->arg_text . '<span class="text-secondary text-sm ml-2">' . $clauseArg->field_type . '</span>',
@@ -251,7 +252,7 @@ class StatTreeController extends Controller
                         "disabled" => false,
                         "selected" => false,
                     ],
-                    "system" => [
+                    "data" => [
                         "type" => "clause_arg",
                         "id" => $clauseArg->id,
                         "uid" => $clauseArg->uid,
@@ -260,6 +261,12 @@ class StatTreeController extends Controller
                         "clauseId" => $clause->id
                     ]
                 ];
+                $argsSystem[] = [
+                    "arg_text" => $clauseArg->arg_text,
+                    "field_type" => $clauseArg->field_type,
+                    "default_value" => null,
+                    "access_level" => null
+                ];
             }
 
             $nodes[] = [
@@ -270,7 +277,7 @@ class StatTreeController extends Controller
                     "selected" => false,
                 ],
                 "children" => $children,
-                "system" => [
+                "data" => [
                     "type" => "clause",
                     "id" => $clause->id,
                     "uid" => $clause->uid,
@@ -279,7 +286,8 @@ class StatTreeController extends Controller
                     "answer" => $clause->answer,
                     "label" => $clause->label,
                     "clauseText" => $clause->clause_text,
-                    "clauseId" => $clause->id
+                    "clauseId" => $clause->id,
+                    "args" => $argsSystem
                 ]
             ];
         }
@@ -296,6 +304,8 @@ class StatTreeController extends Controller
     }
 
     private function lineObject(StatTreeLine  $line) {
+
+        // columns
         $columns = [];
         foreach ($line->reportColumns as $column) {
             $columns[] = [
@@ -304,26 +314,26 @@ class StatTreeController extends Controller
             ];
         }
 
-        $clauses = [];
-        foreach($line->lineClauses as $lineClause) {
-            $args = [];
-            foreach($lineClause->statTreeLineClauseArgs as $stlcArg) {
-                if($stlcArg->clauseArg) {
-                    $args[] = [
-                        "arg_text" => $stlcArg->clauseArg->arg_text,
-                        "field_type" => $stlcArg->clauseArg->field_type,
-                        "default_value" => $stlcArg->default_value,
-                        "access_level" => $stlcArg->access_level
-                    ];
-                }
+        // clause
+        $lineClause = $line->displayLineClause();
+        $args = [];
+        foreach($lineClause->statTreeLineClauseArgs as $stlcArg) {
+            if($stlcArg->clauseArg) {
+                $args[] = [
+                    "arg_text" => $stlcArg->clauseArg->arg_text,
+                    "field_type" => $stlcArg->clauseArg->field_type,
+                    "default_value" => $stlcArg->default_value,
+                    "access_level" => $stlcArg->access_level
+                ];
             }
-            $clauses[] = [
-                "clause_id" => $lineClause->clause_id,
-                "clause_label" => $lineClause->clause_label,
-                "position_index" => $lineClause->position_index,
-                "args" => $args
-            ];
         }
+        $clause = [
+            "clause_id" => $lineClause->clause_id,
+            "clause_label" => $lineClause->clause_label,
+            "position_index" => $lineClause->position_index,
+            "args" => $args
+        ];
+
         $children = [];
         foreach ($line->children as $child) {
             $children[] = $this->lineObject($child);
@@ -336,7 +346,7 @@ class StatTreeController extends Controller
                 "selected" => false,
             ],
             "children" => $children,
-            "system" => [
+            "data" => [
                 "type" => "stat_tree_line",
                 "id" => $line->id,
                 "uid" => $line->uid,
@@ -344,7 +354,7 @@ class StatTreeController extends Controller
                 "lastRefreshCount" => $line->last_refresh_count,
                 "treeOrderPositionIndex" => $line->tree_order_position_index,
                 "columns" => $columns,
-                "clauses" => $clauses
+                "clause" => $clause
             ]
         ];
     }
@@ -392,48 +402,80 @@ class StatTreeController extends Controller
         $statTreeLine->parent_stat_tree_line_id = $parentLine ? $parentLine->id : null;
         $statTreeLine->save();
 
-        // clauses
-        for ($i=0; $i<count($line->clauses); $i++) {
-            $clause = Clause::where('label', $line->clauses[$i]->clause_label)->where('model', 'ilike', $statTree->model)->first();
-            if(!$clause) {
-                return $this->fail('No clause record found for ' . $line->clauses[$i]->clause_label);
-            }
-            $nextId = DB::select("select nextval('stat_tree_line_clause_id_seq')");
-            $statTreeLineClause = new StatTreeLineClause;
-            $statTreeLineClause->id = $nextId[0]->nextval;
-            $statTreeLineClause->uid = Uuid::uuid4();
-            $statTreeLineClause->stat_tree_line_id = $statTreeLine->id;
-            $statTreeLineClause->clause_id = $clause->id;
-            $statTreeLineClause->clause_label = $line->clauses[$i]->clause_label;
-            $statTreeLineClause->position_index = $i;
-            $statTreeLineClause->stat_tree_id = $statTree->id;
-            $statTreeLineClause->save();
-
-            // args
-            foreach ($clause->clauseArgs as $clauseArg) {
-                $statTreeLineClauseArg = new StatTreeLineClauseArg();
-                $nextId = DB::select("select nextval('stat_tree_line_clause_arg_id_seq')");
-                $statTreeLineClauseArg->id = $nextId[0]->nextval;
-                $statTreeLineClauseArg->stat_tree_line_clause_id = $statTreeLineClause->id;
-                $statTreeLineClauseArg->clause_arg_id = $clauseArg->id;
-                $statTreeLineClauseArg->default_value = null;
-                $statTreeLineClauseArg->access_level = 'ADMIN';
-
-                // find the arg matching text and type from line->args and use that
-                for ($j=0; $j<count($line->clauses[$i]->args); $j++) {
-                    if($line->clauses[$i]->args[$j]->arg_text === $clauseArg->arg_text &&
-                        $line->clauses[$i]->args[$j]->field_type === $clauseArg->field_type) {
-                        $statTreeLineClauseArg->default_value = $line->clauses[$i]->args[$j]->default_value;
-                        $statTreeLineClauseArg->access_level = $line->clauses[$i]->args[$j]->access_level;
-                        break;
-                    }
+        // -- clauses -- START
+
+        // copy parent line clauses
+        $maxParentClausePositionIndex = 0;
+        if($parentLine && count($parentLine->lineClauses)) {
+            foreach ($parentLine->lineClauses as $parentLineClause) {
+                $nextId = DB::select("select nextval('stat_tree_line_clause_id_seq')");
+                $statTreeLineClause = new StatTreeLineClause();
+                $statTreeLineClause->id = $nextId[0]->nextval;
+                $statTreeLineClause->uid = Uuid::uuid4();
+                $statTreeLineClause->stat_tree_line_id = $statTreeLine->id; // this stat tree line
+                $statTreeLineClause->clause_id = $parentLineClause->clause_id;
+                $statTreeLineClause->clause_label = $parentLineClause->clause_label;
+                $statTreeLineClause->position_index = $parentLineClause->position_index;
+                if($statTreeLineClause->position_index > $maxParentClausePositionIndex) {
+                    $maxParentClausePositionIndex = $statTreeLineClause->position_index;
                 }
+                $statTreeLineClause->stat_tree_id = $statTree->id;
+                $statTreeLineClause->save();
 
-                $statTreeLineClauseArg->stat_tree_id = $statTree->id;
-                $statTreeLineClauseArg->save();
+                // copy clause args for parent line clauses
+                foreach ($parentLineClause->statTreeLineClauseArgs as $parentLineClauseArg) {
+                    $statTreeLineClauseArg = new StatTreeLineClauseArg();
+                    $nextId = DB::select("select nextval('stat_tree_line_clause_arg_id_seq')");
+                    $statTreeLineClauseArg->id = $nextId[0]->nextval;
+                    $statTreeLineClauseArg->stat_tree_line_clause_id = $statTreeLineClause->id; // this line cause
+                    $statTreeLineClauseArg->clause_arg_id = $parentLineClauseArg->clause_arg_id;
+                    $statTreeLineClauseArg->default_value = $parentLineClauseArg->default_value;
+                    $statTreeLineClauseArg->access_level = $parentLineClauseArg->access_level;
+                    $statTreeLineClauseArg->stat_tree_id = $statTree->id;
+                    $statTreeLineClauseArg->save();
+                }
             }
+        }
 
+        // create own line clause (from clause.clause_label)
+        $clause = Clause::where('label', $line->clause->clause_label)->where('model', 'ilike', $statTree->model)->first();
+        if(!$clause) {
+            return $this->fail('No clause record found for ' . $line->clause->clause_label);
         }
+        $nextId = DB::select("select nextval('stat_tree_line_clause_id_seq')");
+        $statTreeLineClause = new StatTreeLineClause();
+        $statTreeLineClause->id = $nextId[0]->nextval;
+        $statTreeLineClause->uid = Uuid::uuid4();
+        $statTreeLineClause->stat_tree_line_id = $statTreeLine->id; // this stat tree line
+        $statTreeLineClause->clause_id = $clause->id;
+        $statTreeLineClause->clause_label = $line->clause->clause_label;
+        $statTreeLineClause->position_index = $maxParentClausePositionIndex + 1;
+        $statTreeLineClause->stat_tree_id = $statTree->id;
+        $statTreeLineClause->save();
+
+        // create args for own line clause
+        foreach ($clause->clauseArgs as $clauseArg) {
+            $statTreeLineClauseArg = new StatTreeLineClauseArg();
+            $nextId = DB::select("select nextval('stat_tree_line_clause_arg_id_seq')");
+            $statTreeLineClauseArg->id = $nextId[0]->nextval;
+            $statTreeLineClauseArg->stat_tree_line_clause_id = $statTreeLineClause->id;
+            $statTreeLineClauseArg->clause_arg_id = $clauseArg->id;
+            $statTreeLineClauseArg->default_value = null;
+            $statTreeLineClauseArg->access_level = 'ADMIN';
+            for ($j=0; $j<count($line->clause->args); $j++) { // find the arg matching text and type from line->args and use that
+                if($line->clause->args[$j]->arg_text === $clauseArg->arg_text &&
+                    $line->clause->args[$j]->field_type === $clauseArg->field_type) {
+                    $statTreeLineClauseArg->default_value = $line->clause->args[$j]->default_value;
+                    $statTreeLineClauseArg->access_level = $line->clause->args[$j]->access_level;
+                    break;
+                }
+            }
+            $statTreeLineClauseArg->stat_tree_id = $statTree->id;
+            $statTreeLineClauseArg->save();
+        }
+
+        // -- clauses -- END
+
 
         // columns
         for ($i=0; $i<count($line->columns); $i++) {

+ 162 - 97
resources/views/app/stat-tree/stat-trees/sub/edit.blade.php

@@ -174,10 +174,11 @@
             <div class="col-5 pr-0 pl-3 border-left tree-column">
                 <div class="d-flex align-items-baseline mb-2">
                     <h6 class="font-weight-bold m-0">{{$statTree->name}}</h6>
+                    <span class="text-danger d-none if-changed ml-2 text-sm">(modified *)</span>
                     <a href="#" id="refresh-counts" class="ml-3">Refresh Counts</a>
                     <a href="#" class="tree-expand-all ml-3" title="Expand All"><i class="fa fa-angle-double-down text-secondary"></i></a>
                     <a href="#" class="tree-collapse-all ml-2" title="Collapse All"><i class="fa fa-angle-double-up text-secondary"></i></a>
-                    <div class="d-none if-changed ml-auto mr-2">
+                    <div class="ml-auto mr-2 d-none if-changed">
                         <a href="#" class="btn btn-sm btn-primary text-white" id="btn-save-tree">Save</a>
                         <a href="#" class="ml-2 btn btn-sm btn-default border text-dark" onclick="return fastReload()">Reset</a>
                     </div>
@@ -186,26 +187,8 @@
                 @if(!$statTree->rootLines || !count($statTree->rootLines))
                     <p class="text-sm text-secondary font-italic">Drag clauses from the left and drop it above to begin.</p>
                 @endif
-
-                <!-- hidden moes invoked due to context actions -->
-                <div class="border mb-3 p-2 position-absolute" style="left: -10000px; top: -10000px;">
-                    <div class="d-inline-flex align-items-baseline">
-
-                        <!-- remove line -->
-                        <div moe relative center id="remove-line-moe">
-                            <a href="#" start show><i class="text-sm fa fa-trash-alt on-hover-opaque text-danger"></i></a>
-                            <form url="{{ route("practice-management.api.statTreeLine.remove") }}" center hook="reloadStatTree">
-                                @csrf
-                                <input type="hidden" name="uid">
-                                <p>Are you sure?</p>
-                                <div class="d-flex align-items-center">
-                                    <button class="btn btn-sm btn-danger mr-2" type="button" submit>Remove</button>
-                                    <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
-                                </div>
-                            </form>
-                        </div>
-
-                    </div>
+                <div class="d-flex align-items-baseline">
+                    <a href="#" class="log-tree mr-2">Log</a>
                 </div>
 
             </div>
@@ -219,7 +202,7 @@
                     </div>
                     <div class="d-flex align-items-baseline mb-2">
                         <h6 class="font-weight-bold m-0 text-secondary">Argument Values</h6>
-                        <a href="#" class="ml-3 text-sm" onclick="$('.parent-arg').toggle(); return false;">Toggle Parent Clause Args</a>
+                        <!--<a href="#" class="ml-3 text-sm" onclick="$('.parent-arg').toggle(); return false;">Toggle Parent Clause Args</a>-->
                     </div>
                     <div>
                         <table class="table table-sm table-bordered table-striped">
@@ -281,8 +264,8 @@
                     let moe = $(_id);
                     moe.find('input[name], select[name]').each(function() {
                         let name = $(this).attr('name');
-                        if(!!_node.original.system[name]) {
-                            $(this).val(!!_node.original.system[name] ? _node.original.system[name] : '');
+                        if(!!_node.data[name]) {
+                            $(this).val(!!_node.data[name] ? _node.data[name] : '');
                         }
                     });
                     moe.find('a[start]').trigger('click');
@@ -312,14 +295,13 @@
                                     },
                                     "dnd": {
                                         "is_draggable": function(_node, _e) {
-                                            return _node[0].original.system.type === 'clause';
+                                            return _node[0].data.type === 'clause';
                                         }
                                     },
                                     "contextmenu": {
                                         show_at_node: false,
                                         items: function (node) {
-                                            console.log('ALIX',node.original.system)
-                                            if(node.original.system.type === 'clause') { // clause
+                                            if(node.data.type === 'clause') { // clause
                                                 return {
                                                     "edit": {
                                                         "label": "<span class='text-sm'>Edit Clause</span>",
@@ -341,7 +323,7 @@
                                                     }
                                                 }
                                             }
-                                            else if(node.original.system.type === 'clause_arg') { // clause
+                                            else if(node.data.type === 'clause_arg') { // clause
                                                 return {
                                                     "edit": {
                                                         "label": "<span class='text-sm'>Edit Arg</span>",
@@ -373,6 +355,18 @@
                 // stat tree
                 let StatTree = {
                     el: $('#stat-tree-view-{{$statTree->id}}'),
+                    changed: false,
+
+                    setDirty: function(_changed = true) {
+                        this.changed = _changed;
+                        if(_changed) {
+                            $('.if-changed').removeClass('d-none');
+                        }
+                        else {
+                            $('.if-changed').addClass('d-none');
+                        }
+                    },
+
                     load: function() {
 
                         // destroy if existing
@@ -386,10 +380,8 @@
 
                             // init tree with data
                             this.el
-                                .on('move_node.jstree', function(node, original) { StatTree.dropped(node, original); })
-                                .on('copy_node.jstree', function() { StatTree.dropped.apply(StatTree, arguments); })
-                                // .on('move_node.jstree', function() { StatTree.save(); })
-                                // .on('copy_node.jstree', function() { StatTree.save(); })
+                                .on('move_node.jstree', (_e, _data) => { StatTree.dropped(_e, _data); })
+                                .on('copy_node.jstree', (_e, _data) => { StatTree.dropped(_e, _data); })
                                 .on('select_node.jstree', () => { StatTree.onSelected(); })
                                 .on('deselect_node.jstree', () => { StatTree.onDeselected(); })
                                 .jstree({
@@ -401,22 +393,21 @@
                                     },
                                     "dnd": {
                                         "is_draggable": function(_node, _e) {
-                                            return _node[0].original.system && _node[0].original.system.type === 'stat_tree_line';
+                                            return _node[0].data && _node[0].data.type === 'stat_tree_line';
                                         }
                                     },
                                     "contextmenu": {
                                         show_at_node: false,
                                         items: function (node) {
-                                            console.log('ALIX',node);
-                                            if(node.original.system.type === 'stat_tree_line') { // stat_tree_line
+                                            if(node.data.type === 'stat_tree_line') { // stat_tree_line
                                                 return {
 
                                                     "data": {
                                                         "label": "<span class='text-sm'>View Data</span>",
                                                         "action": function (obj) {
-                                                            openDynamicStagPopup('/practice-management/stat-tree-lines/view-data/' + node.original.system.uid,
+                                                            openDynamicStagPopup('/practice-management/stat-tree-lines/view-data/' + node.data.uid,
                                                                 null,
-                                                                node.original.system.displayLabel,
+                                                                node.data.displayLabel,
                                                                 false,
                                                                 'medium');
                                                         },
@@ -424,7 +415,11 @@
                                                     "remove": {
                                                         "label": "<span class='text-sm'>Remove Node</span>",
                                                         "action": function (obj) {
-                                                            fillAndInvokeMoe('#remove-line-moe', node);
+                                                            let selected = StatTree.selectedNode();
+                                                            if(selected) {
+                                                                StatTree.el.jstree(true).delete_node(selected.id);
+                                                                StatTree.setDirty();
+                                                            }
                                                         }
                                                     }
                                                 }
@@ -442,29 +437,95 @@
                     },
 
                     dropped: function(_e, _data) {
-                        if(_data.original && _data.original.original && _data.original.original.system &&
-                            _data.original.original.system.type === 'clause') {
-                            _data.node.original.system = _data.original.original.system;
+
+                        if(_data.original && _data.original.data &&
+                            _data.original.data.type === 'clause') {
+                            _data.node.data = {
+                                type:  "stat_tree_line",
+                                displayLabel: _data.original.data.label,
+                                columns:  [],
+                                clause:  {
+                                    clause_id: _data.original.data.clauseId,
+                                    clause_label: _data.original.data.label,
+                                    args: _data.original.data.args
+                                }
+                            };
+
+                            // delete args and query children (from the clauses tree)
+                            this.el.jstree(true).delete_node(_data.node.children);
+
+                            // open new parent to reveal dropped node
+                            this.el.jstree(true).open_node(_data.parent);
+
+                            // select the new node
+                            this.el.jstree(true).deselect_all();
+                            this.el.jstree(true).select_node(_data.node.id);
                         }
-                        StatTree.save();
+                        else if(_e.type === 'copy_node' && _data.original && _data.original.data &&
+                            _data.original.data.type === 'stat_tree_line') {
+
+                            function recursiveCopyData(src, target) {
+                                src = StatTree.el.jstree(true).get_node(src);
+                                target = StatTree.el.jstree(true).get_node(target);
+                                target.data = JSON.parse(JSON.stringify(src.data));
+                                if(src.children.length === target.children.length) {
+                                    for (let i = 0; i < target.children.length; i++) {
+                                        recursiveCopyData(src.children[i], target.children[i])
+                                    }
+                                }
+                                else {
+                                    console.log('Child count not the same!', src.text, target.text);
+                                }
+                            }
+
+                            recursiveCopyData(_data.original.id, _data.node.id);
+
+                            // open new parent to reveal dropped node
+                            this.el.jstree(true).open_node(_data.parent);
+
+                            // select the new node
+                            this.el.jstree(true).deselect_all();
+                            this.el.jstree(true).select_node(_data.node.id);
+                        }
+
+                        this.setDirty();
                     },
 
                     save: function() {
                         setTimeout(() => {
-                            StatTree.payload();
                             $.post('{{ route("practice-management.api.statTree.replaceAllLinesJSON") }}', {
                                 uid: "{{ $statTree->uid }}",
                                 data: StatTree.payload()
                             }, function (response) {
                                 if(!hasResponseError(response)) {
+                                    toastr.success('All changes saved!');
+                                    StatTree.setDirty(false);
                                     StatTree.load();
-                                    // TODO: saved indicator
                                 }
                             }, 'json');
                             StatTree.onSelected();
                         }, 0);
                     },
 
+                    log: function() {
+                        let raw = this.el.jstree('get_json');
+                        for (let i = 0; i < raw.length; i++) {
+                            this.logNode(raw[i].id, "");
+                        }
+                    },
+
+                    logNode: function(_id, _indent) {
+                        let node = this.el.jstree('get_node', _id);
+                        let s = [], css = false;
+                        if(!node.data) css = true;
+                        s.push($('<div/>').html(node.text).text().substr(0, 10) + '...');
+                        s.push(node.data ? 'data:' + node.data.type: '%cdata:X');
+                        console.log("ALIX: " + _indent + s.join('  '), css ? 'color:red' : '');
+                        for (let i = 0; i < node.children.length; i++) {
+                            this.logNode(node.children[i], _indent + "\t");
+                        }
+                    },
+
                     payload: function() {
                         let raw = this.el.jstree('get_json');
                         let nodes = [];
@@ -474,32 +535,18 @@
                         return JSON.stringify(nodes);
                     },
 
-                    nodePayload: function(_id, _parentClauses = null) {
+                    nodePayload: function(_id) {
                         let node = this.el.jstree('get_node', _id);
                         let payload = {};
-                        let clauses = _parentClauses ? JSON.parse(JSON.stringify(_parentClauses)) : [];
-                        if(node.original.system.type === 'clause') {
-                            clauses.push({
-                                clause_label: node.original.system.label,
-                                args: []
-                            });
+                        if(node.data.type === 'stat_tree_line') {
                             payload = {
-                                displayLabel: node.original.system.label,
-                                clauses: clauses,
-                                columns: [],
-                                children: [],
-                            };
-                        }
-                        else if(node.original.system.type === 'stat_tree_line') {
-                            clauses.push(node.original.system.clauses[node.original.system.clauses.length - 1]);
-                            payload = {
-                                displayLabel: node.original.system.displayLabel,
-                                clauses: clauses,
-                                columns: node.original.system.columns,
+                                displayLabel: node.data.displayLabel,
+                                clause: node.data.clause,
+                                columns: node.data.columns,
                             };
                             let children = [];
                             for (let i = 0; i < node.children.length; i++) {
-                                children.push(this.nodePayload(node.children[i], clauses));
+                                children.push(this.nodePayload(node.children[i]));
                             }
                             payload.children = children;
                         }
@@ -517,7 +564,7 @@
                     getSelectedNodeArgs: function() {
                         let selected = this.selectedNode();
                         if(selected) {
-                            return selected.original.system.args;
+                            return selected.data.args;
                         }
                         return [];
                     },
@@ -525,7 +572,7 @@
                     getSelectedNodeColumns: function() {
                         let selected = this.selectedNode();
                         if(selected) {
-                            return selected.original.system.columns;
+                            return selected.data.columns;
                         }
                         return [];
                     },
@@ -533,7 +580,8 @@
                     setSelectedNodeColumns: function(columns) {
                         let selected = this.selectedNode();
                         if(selected) {
-                            selected.original.system.columns = columns;
+                            selected.data.columns = columns;
+                            this.setDirty();
                         }
                         return [];
                     },
@@ -545,34 +593,32 @@
                     onSelected: function(_e, _data) {
                         let selected = this.selectedNode();
 
-                        if(!(selected && selected.original && selected.original.system && selected.original.system.type === 'stat_tree_line')) return;
+                        if(selected) console.log(selected.data)
 
-                        console.log('ALIX',selected.original.system)
+                        if(!(selected && selected.data && selected.data.type === 'stat_tree_line')) return;
 
                         linePropsColumn.removeClass('d-none');
-                        linePropsColumn.find('[line-label]').text(selected.original.system.displayLabel);
+                        linePropsColumn.find('[line-label]').text(selected.data.displayLabel);
 
                         // fill args
                         let tbody = linePropsColumn.find('[line-args]');
-                        let clauses = selected.original.system.clauses;
+                        let clause = selected.data.clause;
                         tbody.empty();
-                        if(clauses && clauses.length) {
-                            for (let j = 0; j < clauses.length; j++) {
-                                let edit = j === clauses.length - 1;
-                                let args = clauses[j].args ? clauses[j].args : [];
-                                $('<tr/>').addClass(edit ? '' : 'opacity-60 parent-arg').append($('<td/>').attr('colspan', 3).addClass('font-weight-bold text-sm ' + (edit ? '' : 'text-secondary')).text((j+1) + '. ' + clauses[j].clause_label)).appendTo(tbody);
-                                if(!args.length) {
-                                    $('<tr/>').addClass(edit ? '' : 'opacity-60 parent-arg').append($('<td/>').attr('colspan', 3).addClass('pl-3 text-secondary text-sm').text('No args')).appendTo(tbody);
-                                }
-                                else {
-                                    for (let i = 0; i < args.length; i++) {
-                                        $('<tr/>')
-                                            .addClass(edit ? '' : 'opacity-60 parent-arg')
-                                            .append($('<td/>').addClass('pl-3').html(args[i].arg_text + ' <span class="text-secondary text-sm">(' + args[i].field_type + ')</span>'))
-                                            .append($('<td/>').text(args[i].default_value).append(edit ? '<a href="#" class="edit-arg-value ml-2" data-uid="' + args[i].uid + '"><i class="fa fa-edit text-primary text-sm on-hover-opaque"></i></a>': ''))
-                                            .append($('<td/>').text(args[i].access_level).append(edit ? '<a href="#" class="edit-arg-access-level ml-2" data-uid="' + args[i].uid + '"><i class="fa fa-edit text-primary text-sm on-hover-opaque"></i></a>': ''))
-                                            .appendTo(tbody);
-                                    }
+                        if(clause) {
+                            let edit = true;
+                            let args = clause.args ? clause.args : [];
+                            // $('<tr/>').addClass(edit ? '' : 'opacity-60 parent-arg').append($('<td/>').attr('colspan', 3).addClass('font-weight-bold text-sm ' + (edit ? '' : 'text-secondary')).text(clause.clause_label)).appendTo(tbody);
+                            if(!args.length) {
+                                $('<tr/>').addClass(edit ? '' : 'opacity-60 parent-arg').append($('<td/>').attr('colspan', 3).addClass('pl-3 text-secondary text-sm').text('No args')).appendTo(tbody);
+                            }
+                            else {
+                                for (let i = 0; i < args.length; i++) {
+                                    $('<tr/>')
+                                        .addClass(edit ? '' : 'opacity-60 parent-arg')
+                                        .append($('<td/>').addClass('pl-3').html(args[i].arg_text + ' <span class="text-secondary text-sm">(' + args[i].field_type + ')</span>'))
+                                        .append($('<td/>').text(args[i].default_value).append(edit ? '<a href="#" class="edit-arg-value ml-2" data-uid="' + args[i].uid + '"><i class="fa fa-edit text-primary text-sm on-hover-opaque"></i></a>': ''))
+                                        .append($('<td/>').text(args[i].access_level).append(edit ? '<a href="#" class="edit-arg-access-level ml-2" data-uid="' + args[i].uid + '"><i class="fa fa-edit text-primary text-sm on-hover-opaque"></i></a>': ''))
+                                        .appendTo(tbody);
                                 }
                             }
                         }
@@ -609,9 +655,10 @@
                             label: _data.label,
                             display_key: _data.text
                         });
+                        $(_input).val('').focus();
                         StatTree.setSelectedNodeColumns(columns);
-                        $(_input).val('');
-                        StatTree.save();
+                        StatTree.onSelected();
+                        StatTree.setDirty();
                         return false;
                     });
 
@@ -626,7 +673,8 @@
                             columns[index] = x;
                         }
                         StatTree.setSelectedNodeColumns(columns);
-                        StatTree.save();
+                        StatTree.onSelected();
+                        StatTree.setDirty();
                         return false;
                     });
 
@@ -641,7 +689,8 @@
                             columns[index] = x;
                         }
                         StatTree.setSelectedNodeColumns(columns);
-                        StatTree.save();
+                        StatTree.onSelected();
+                        StatTree.setDirty();
                         return false;
                     });
 
@@ -651,7 +700,8 @@
                         let columns = StatTree.getSelectedNodeColumns();
                         columns.splice(+($(this).attr('data-index')), 1);
                         StatTree.setSelectedNodeColumns(columns);
-                        StatTree.save();
+                        StatTree.onSelected();
+                        StatTree.setDirty();
                         return false;
                     });
 
@@ -681,6 +731,20 @@
                         return false;
                     });
 
+                $(document)
+                    .off('click', '#btn-save-tree')
+                    .on('click', '#btn-save-tree', function() {
+                        StatTree.save();
+                        return false;
+                    });
+
+                $(document)
+                    .off('click', '.log-tree')
+                    .on('click', '.log-tree', function() {
+                        StatTree.log();
+                        return false;
+                    });
+
                 $(document)
                     .off('input change paste', '.frm-clause-add-edit input[name="question"], .frm-clause-add-edit input[name="answer"]')
                     .on('input change paste', '.frm-clause-add-edit input[name="question"], .frm-clause-add-edit input[name="answer"]', function() {
@@ -693,6 +757,11 @@
                 $('#refresh-counts')
                     .off('click')
                     .on('click', function() {
+                        if(StatTree.changed) {
+                            if(!window.confirm('Tree has not been saved. Changes will be lost if you continue. Continue?')) {
+                                return;
+                            }
+                        }
                         showMask();
                         $.post("{{ route('practice-management.api.statTree.refreshTreeCountQueries') }}", {
                             statTreeID: "{{ $statTree->id }}"
@@ -712,10 +781,6 @@
                     ClausesTree.load();
                 });
 
-                addMCHook('reloadStatTree', function() {
-                    StatTree.load();
-                });
-
             }
             addMCInitializer('stat-tree-edit-page', init, '#statTreeEdit');
         }).call(window);