Inline editing an expanding array with Angular

Mechanics behind a growing array of items that can be edited inline. This is the variant that expands the original collection inline. See also http://iweinfuld.posterous.com/expanding-collections-with-inline-edit-in-ang

by iweinfuld

HTML

<script src="http://code.angularjs.org/1.1.0/angular.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">
<div ng-app="app" class="container" ng-controller="Ctrl">
    <div  class="row">
        <div class="span5 well" >
            
          <div editable-collection="item in items" expand-on="value">            
              <div class="row">
                <input  type="text" ng-model="item.value" class="span2"/>
                <button class="btn span1 pull-right" 
                    ng-click="remove($index)">Delete</button>              
              </div>
          </div>
          
        </div>   
    </div>  
    <div class="row"><div class="span5 well">items:{{items|json}}</div></div>
    <div class="row"><div class="span5 well">overflow:{{overflow|json}}</div></div>
</div>

JavaScript

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

/**
 * Creates an ng-repeat with an overflow area to enable inline editing with overflow.
 * 
 * Nesting is not supported.
 */
app.directive('editableCollection', function factory($compile) {
    return {
        link: function(scope, el, attrs, controller) {

            //parse the element and attributes
            if (!attrs.expandOn) {
                throw 'Expected exand-on attribute on element: ' + el.html() + '\n but found: ' + JSON.stringify(attrs.$attr) + '\n';
            }

            var split = attrs.editableCollection.split(' ');
            var elementName = split[0];
            var collectionName = split[2];

            if (split.length != 3 || split[1] != 'in') {
                throw 'Expected editable collection defined as "{element} in {collection}" got: ' + attrs.editableCollection;
            }

            //Take the elements from this one to move to a child ng-repeat
            var original = el.contents();

            //Add the ng-repeat for the actual collection
            var repeater = angular.element('<div/>');
            repeater.attr('ng-repeat', attrs.editableCollection);
            repeater.append(original);

            //Add the ng-repeat for the overflow
            scope[collectionName].push({});
            el.append(repeater);

            //Watch the first element and expand the overflow if needed
            scope.lastOverflowExpander = function() {
                return scope[collectionName].slice(-1)[0][attrs.expandOn];
            };


            scope.$watch('lastOverflowExpander()', function(value) {
                console.log('expanding for ' + value);
                if (value) {
                    scope[collectionName].push({});
                }
            });

            //compile everything so that Angular can link the new stuff
            $compile(el.contents())(scope);
        }
    };
});

app.controller('Ctrl', function($scope) {
   ...