Angular Edit-in-Place Tree Directive

by Warwick Grigg

HTML

<div>
  <div ng-controller="TreeCtrl">
    <h2>Tree - the node names can be edited in place by clicking</h2>
    <br />

 	<script type="text/ng-template" id="tree_item_renderer.html">
		<button ng-click="expand_collapse(data)" ng-show="data.show && data.nodes.length > 0">-</button>
    	<button ng-click="expand_collapse(data)" ng-show="!data.show && data.nodes.length > 0">+</button>
    	<edit-in-place value="data"></edit-in-place>
     <ol ng-show="data.show">
        <li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer.html'"></li>
    </ol>
	</script>

	<div id="tree">

		<ol >
		    <li ng-repeat="data in tree" ng-include="'tree_item_renderer.html'"></li>
		</ol>	
	</div>
     
    <br />
    <p>Here we repeat the contacts to ensure bindings work
    </p>
      
	<script type="text/ng-template" id="tree_item_renderer2.html">
		<button ng-click="expand_collapse(data)" ng-show="data.show && data.nodes.length > 0">-</button>
    	<button ng-click="expand_collapse(data)" ng-show="!data.show && data.nodes.length > 0">+</button>
    	{{data.name}}
     <ol ng-show="data.show">
        <li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer2.html'"></li>
    </ol>
	</script>

	<div id="tree2">

		<ol>
		    <li ng-repeat="data in tree" ng-include="'tree_item_renderer2.html'"></li>
		</ol>	
	</div>
       
  </div>
</div>

CSS

ol {
  list-style-type: none;
  margin: 0;
  padding: 0;
}
 
li {
  font: 200 14px/1.5 Helvetica, Verdana, sans-serif;
  margin-left: 20px;
 }

button {
    background:transparent;
    border:none;
    outline:none;
    cursor:pointer;
}

.edit-in-place span {
  cursor: pointer;
}

.edit-in-place input {
  display: none;
}

.edit-in-place.active span {
  display: none;
}

.edit-in-place.active input {
  display: inline-block;
}

JavaScript

var app = angular.module( 'myApp', [] );

app.directive( 'editInPlace', function() {
  return {
    restrict: 'E',
    scope: { value: '=' },
      
    template: '<span ng-click="edit()" ng-bind="value.name"></span><input ng-model="value.name"></input>',
    link: function ( $scope, element, attrs ) {
      // Let's get a reference to the input element, as we'll want to reference it.
      var inputElement = angular.element( element.children()[1] );
      
      // This directive should have a set class so we can style it.
      element.addClass( 'edit-in-place' );
      
      // Initially, we're not editing.
      $scope.editing = false;
      
      // ng-click handler to activate edit-in-place
      $scope.edit = function () {
        $scope.editing = true;
        
        // We control display through a class on the directive itself. See the CSS.
        element.addClass( 'active' );
        
        // And we must focus the element. 
        // `angular.element()` provides a chainable array, like jQuery so to access a native DOM function, 
        // we have to reference the first element in the array.
        inputElement[0].focus();
      };
      
      // When we leave the input, we're done editing.
      inputElement.prop( 'onblur', function() {
        $scope.editing = false;
        element.removeClass( 'active' );
      });
    }
  };
});

app.controller("TreeCtrl", function($scope) {
	$scope.expand_collapse = function(data) {
		data.show = !data.show;
	}

    // below is an array of size 1 - it does not have to be that way
	$scope.tree = [ {
					name : "Root",
					show : true,
					nodes : []
				} ];
    
     var nodeChild1 = {
		name : "Child 1",
		show : false,
		nodes : []
	};
     var nodeChild2 = {
		name : "Child 2",
		show : false,
		nodes : []
	};
    // Add the children
    $scope.tree[0].nodes.push(nodeChild1);
    $scope.tree[0].nodes.push(nodeChild2);
    
    var nodeGrandChild1 = {
		name : "Grand Child 1",
		show :...