Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check for duplicate value in recursive array structure on textbox blur event?

I am trying have unique title to my node in recursive tree.

So when I give title to my nodes it should check that this title is already taken by some other nodes or not. If taken then it should alert user and it should reset that node value to previous value.

No two nodes should have same title.

But here as structure is recursive so I am not getting how do to do this.

Note : I want to do this as soon as textbox loses its focus.

var app = angular.module("myApp", []);
        app.controller("TreeController", function ($scope) {
            $scope.delete = function (data) {
                data.nodes = [];
            };
            $scope.add = function (data) {
                var post = data.nodes.length + 1;
                var newName = data.name + '-' + post;
                data.nodes.push({ name: newName, nodes: [],selected : false, myObj: { name: newName} });
            };
            $scope.tree = [{ name: "Node", nodes: [], selected: false }];

            $scope.setActive = function ($event, data) {
            	$event.stopPropagation();
                $scope.selectedData = data;
                clearDivSelection($scope.tree);
                data.selected = true;
            };

            function clearDivSelection(items) {
                items.forEach(function (item) {
                    item.selected = false;
                    if (item.nodes) {
                        clearDivSelection(item.nodes);
                    }
                });
            }
            
            $scope.checkDuplicateNodeName = function () {
             alert()
            }
        });
ul {
    list-style: circle;
}
li {
    margin-left: 20px;
}
 .active { background-color: #ccffcc;}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>


<ul ng-app="myApp" ng-controller="TreeController">
        <li ng-repeat="data in tree" ng-include="'tree_item_renderer.html'"></li>
        <script type="text/ng-template" id="tree_item_renderer.html">
            <div ng-class="{'active': data.selected}" > {{data.myObj.name}}</div>
            <button ng-click="add(data)">Add node</button>
            <button ng-click="delete(data)" ng-show="data.nodes.length > 0">Delete nodes</button>
            <ul>
                <li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer.html'" ng-click="setActive($event, data)"></li>
            </ul>
        </script>
        <div style="margin-left:100px;">
           Title :  <input type="text" ng-model="selectedData.myObj.name" ng-blur="checkDuplicateNodeName()" />
           Location :  <input type="text" ng-model="selectedData.myObj.location" />

        </div>
    </ul>
like image 894
Learning-Overthinker-Confused Avatar asked May 19 '17 12:05

Learning-Overthinker-Confused


2 Answers

my solution

  1. store validated copy of name on blur (meaning there are 3 names but I dont know the point of myObj.name so I left it as is, and you can clean up)
  2. find dupes recursively, stop on first true. if there's a dupe, use last valid name, else update last valid name.

whys

  1. you want to validate the name only on blur. angular provides ways to validate ng-model on every change via, parsers and formatters which would completely restrict the user from persisting a dupe name. there are a ton of ways to handle this problem (validators, editing without persisting) and all require different kinds of solutions. explore and go with what works best for you
  2. using a hashMap (my first thought) would require cleanup logic on renames and deletes that would've ended up complicated the code further

If you wish to search for dupes only in the node's tree, you need to store a reference to the parent node, and use the getTree method to determine the root node to search from.

var app = angular.module("myApp", []);
app.controller("TreeController", function($scope) {
  $scope.delete = deleteNodes;
  $scope.add = add;
  $scope.setActive = setActive;
  $scope.checkDuplicateNodeName = checkDuplicateNodeName;
  $scope.trees = [{
    name: "Node",
    nodes: [],
    selected: false
  },{
    name: "Node2",
    nodes: [],
    selected: false
  }];

  function deleteNodes(data) {
    data.nodes = [];
  }

  function add(data) {
    var post = data.nodes.length + 1;
    var newName = data.name + '-' + post;
    
    data.nodes.push({
      name: newName,
      nodes: [],
      selected: false,
      validatedName: newName
    });
  }

  function setActive($event, data) {
    $event.stopPropagation();
    if($scope.selectedData) {
      $scope.selectedData.selected = false;
    }
    $scope.selectedData = data;
    
    data.selected = true;
  }

  function checkDuplicateNodeName() {
    if(!$scope.selectedData)
      return;
  
    var dupe = false;
    
    for(var idx = 0; idx < $scope.trees.length; idx++) {
      if(isDuplicateName($scope.trees[idx], $scope.selectedData)) {
        dupe = true;
        break;
      }
    }
    
    if(dupe){
      alert('The name "' + $scope.selectedData.name + '" already exists');
      $scope.selectedData.name = $scope.selectedData.validatedName;
    } else {
      $scope.selectedData.validatedName = $scope.selectedData.name;
    }

  }
  
  function getTree(node){
    while(node.parent) {
      node = node.parent;
    }
    
    return node;
  }
  
  function isDuplicateName(node, nodeToCheck) {
    var dupeName = node != nodeToCheck && node.name && nodeToCheck.name && node.name == nodeToCheck.name;
    if(dupeName) return true;
    
    if(node.nodes){
      for(var idx=0; idx< node.nodes.length; idx++) {
        if(isDuplicateName(node.nodes[idx], nodeToCheck)){
          return true;
        }
      }
    }
    return false;
  }
});
ul {
  list-style: circle;
}

li {
  margin-left: 20px;
}

.active {
  background-color: #ccffcc;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>


<ul ng-app="myApp" ng-controller="TreeController">
  <li ng-repeat="data in trees" ng-include="'tree_item_renderer.html'"></li>
  <script type="text/ng-template" id="tree_item_renderer.html">
    <div ng-class="{'active': data.selected}"> {{data.name}}</div>
    <button ng-click="add(data)">Add node</button>
    <button ng-click="delete(data)" ng-show="data.nodes.length > 0">Delete nodes</button>
    <ul>
      <li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer.html'" ng-click="setActive($event, data)"></li>
    </ul>
  </script>
  <div style="margin-left:100px;">
    Title : <input type="text" ng-model="selectedData.name" ng-blur="checkDuplicateNodeName()" /> Location : <input type="text" ng-model="selectedData.myObj.location" />

  </div>
</ul>
like image 158
Bowofola Avatar answered Oct 21 '22 03:10

Bowofola


You could use a recursive approach similar to your clearDivSelection method:

function isDuplicated (node, title) {
  var result = false;

  if (node.nodes && node.nodes.length > 0) result = node.nodes.reduce(function (result, node) {
    return isDuplicated(node, title);
  }, false);

  return result && node.name === title;
}

Or (at a cost of memory), you could maintain a list of titles:

$scope.titles = {};

$scope.add = function (data) {
  var post = data.nodes.length + 1;
  var newName = data.name + '-' + post;

  if ($scope.titles[newName]) return; // refuse to add
  $scope.titles[newName] = true;

  data.nodes.push({
    name: newName,
    nodes: [],
    selected : false,
    myObj: {
      name: newName
    }
  });
};

I'm not sure exactly what you mean by

should reset that node value to previous value.

if you are "add"ing new objects, you won't have a "previous value" - but I'll leave that bit up to you. This should get you started, anyway.

like image 3
Alex McMillan Avatar answered Oct 21 '22 03:10

Alex McMillan