AngularJS Edit-In-Place Directive

by GlobalDomestic

HTML

<script src="http://code.angularjs.org/1.2.5/angular.js"></script>
<script src="http://code.angularjs.org/1.2.5/angular-sanitize.js"></script>
<div ng-controller="MyCtrl">
    <input-edit-in-place ng-model="myModel.foo" type="text" placeholder="Please enter something...">{{ myModel.foo }}<pre>{{ edit | json }}</pre></input-edit-in-place>
    <br>
        <input-edit-in-place ng-model="myModel.bar" type="textarea" placeholder="Please enter something..."><div ng-bind-html="myModel.bar | linky"></div></input-edit-in-place>
    <hr>
    <pre>myModel: {{ myModel | json }}</pre>
</div>

<!-- text.tmpl -->
<script type="text/ng-template" id="text.tmpl">
    <input type="text" autofocus autocomplete="off" placeholder="{{ placeholder }}" ng-model="view.model" ng-keydown="keyDown( $event )">
</script>

<!-- textarea.tmpl -->
<script type="text/ng-template" id="textarea.tmpl">
    <textarea autofocus placeholder="{{ placeholder }}" ng-model="view.model" ng-keydown="keyDown( $event )"></textarea>
</script>

<!-- template.tmpl -->
<script type="text/ng-template" id="template.tmpl">
    <div class="input-edit-in-place">
        <div ng-hide="view.edit" ng-dblclick="showEdit()" ng-transclude></div>
        <div ng-show="view.edit" ng-include="type + '.tmpl'"></div>
    </div>
</script>

CSS

.input-edit-in-place {
    min-width: 100px;
    min-height: 20px;
    border: solid 1px red;
}

[placeholder]::-webkit-input-placeholder {
  color: #a4a4a4;
  font-style: italic;
}

[placeholder]:focus::-webkit-input-placeholder {
  -webkit-transition: opacity 0.5s 5s ease;
  transition: opacity 0.5s 5s ease;
  opacity: 0;
}

JavaScript

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

myApp.controller( 'MyCtrl', [ '$scope', function ( $scope ) {
    $scope.myModel = {
        foo: 'Boop',
        bar: 'This is some longer text and a link to http://www.angularjs.com'
    };
}]);

myApp.directive( 'inputEditInPlace', [ function inputEditInPlaceDirective() {
    return {
        scope: {
            placeholder: '@',
            type: '@',
            model: '=ngModel'
        },
        restrict: 'E', // only activate on element attribute
        templateUrl: 'template.tmpl',
        replace: true,
        transclude: true,
        controller: function inputEditInPlaceController( $scope ) {
            $scope.save = function save() {
                $scope.model = $scope.view.model;
                $scope.hideEdit();
            };

            $scope.keyDown = function keyDown( $event ) {
                // Escape key
                if ( $event.keyCode === 27 ) {
                    $scope.hideEdit();
                }
                else if ( ( $event.ctrlKey || $event.metaKey ) && $event.keyCode === 13 ) {
                    $scope.save();
                }
            };

            $scope.hideEdit = function hideEdit() {
                $scope.view.edit = false;
            };

            $scope.showEdit = function showEdit() {
                $scope.view = {
                    model: $scope.model,
                    edit: true
                };
            };
        }
    };
}]);