Inline editing an expanding array with Angular

Mechanics behind a growing array of items that can be edited inline. See http://iweinfuld.posterous.com/expanding-collections-with-inline-edit-in-ang

by Warwick Grigg

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 class="row" editable-collection="item in items" expanding-on="value">            
            <input  type="text" ng-model="item.value" class="span2"/>
            <p class="span2 pull-right">{{item.value}}</p>            
          </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.expandingOn) {
                throw 'Expected exanding-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);
            el.append(repeater);

            //Add the ng-repeat for the overflow
            var overflowId = 'shouldBeUniquelyGeneratedToAvoidCollisions';
            console.log('Created overflow with id: '+overflowId+ ' for '+collectionName);
            scope[overflowId] = [
                {}
            ];
            var overflow = repeater.clone();
            overflow.attr('ng-repeat',
                elementName + ' in ' + overflowId);
            el.append(overflow);

            //Watch the first element and expand the overflow if needed
            scope.$watch(overflowId+'[0].' + attrs.expandingOn, function (value) {
                if (value && scope[overflowId].length < 2) {
        ...