Edit-in-place means that the text input field will be toggled between edit-mode and preview-mode, and when in edit-mode - you can persist the change to the server and continue working without page refresh.
This approach is becoming common in reactive web-apps and SPAs, while The "save" button for the whole form is gradually disappearing.
My implementation provides a directive, an HTML template, a controller (in the directive) and a service. With this configuration your controllers remain untouched, since the directive's controller is injected with a generic service and uses it directly. The service uses Restangular to talk to my Rails back-end.
So here's the Gist:
Yes, this code still needs some refactoring..
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FoodBetterApp.factory('EditableInput', function(Restangular) { | |
return { | |
saveFieldValue: function(pluralResourceName, resourceId, fieldName, fieldValue) { | |
var postData = {}; | |
postData[fieldName] = fieldValue; | |
return Restangular.one(pluralResourceName, resourceId).put(postData); | |
} | |
}; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
<textarea ng-show="isEditMode" ng-model="fieldValue" rows="2" class="form-control span3" autofocus>{{getPreviewText()}}</textarea> | |
<span ng-click="saveData()" ng-show="isEditMode" class="glyphicon glyphicon-ok clickable"></span> | |
<span ng-click="cancelEdit()" ng-show="isEditMode" class="glyphicon glyphicon-remove clickable"></span> | |
<div ng-hide="isEditMode" ng-click="switchToEditMode()">{{getPreviewText()}}</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FoodBetterApp.directive('editableInput', ['EditableInput', function(EditableInput) { | |
return { | |
restrict: 'E', | |
replace: true, | |
templateUrl: 'partials/editable-text/editable-text.html', | |
scope: { | |
resourceId: '@', | |
resourceNameSingle: '@', | |
resourceNamePlural: '@', | |
fieldName: '@', | |
fieldValue: '@', | |
lastFieldValue: '@fieldValue' | |
}, | |
compile: function(element, attributes, transclude) { | |
return function(scope, element, attributes) { | |
scope.isEditMode = false; | |
scope.switchToEditMode = function() { | |
scope.isEditMode = true; | |
var fieldValueTextArea = $(element).find('textarea'); | |
var textBoxValue = scope.lastFieldValue; | |
fieldValueTextArea.val(''); | |
fieldValueTextArea.blur().focus(); | |
fieldValueTextArea.val(textBoxValue); | |
}; | |
scope.switchToPreviewMode = function() { | |
scope.isEditMode = false; | |
}; | |
scope.saveData = function() { | |
EditableInput.saveFieldValue(scope.resourceNamePlural, scope.resourceId, | |
scope.fieldName, scope.fieldValue).then(function() { | |
scope.lastFieldValue = scope.fieldValue; | |
scope.switchToPreviewMode(); | |
}); | |
}; | |
scope.cancelEdit = function() { | |
scope.fieldValue = scope.lastFieldValue; | |
scope.switchToPreviewMode(); | |
}; | |
scope.getPreviewText = function() { | |
return (scope.fieldValue === '' ? 'click to edit' : scope.fieldValue); | |
}; | |
}; | |
} | |
}; | |
}]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<editable-input resource-id="{{recipe.id}}" resource-name-single="recipe" | |
resource-name-plural="recipes" field-name="notes" field-value="{{recipe.notes}}"></editable-input> |