6

I would like to be able to edit and display complex model in a <textarea> element. Here's the HTML piece for generating model's fields dynamically from JSON response:

<p>parent uuid*: </p>
<input ng-model="parentUuid" capitalize type="text" placeholder="String"
    class="form-control" style="width:200px; display: inline-block;"/> <br/>
<p>resource*:</p>
<select ng-model="childResource" ng-change="loadResourceFields(childResource)" 
    class="form-control" style="width:300px; display: inline-block;">
    <option ng-repeat="childResource in restResources">{{childResource}}</option>
</select>
<div ng-repeat="field in childFields">
    <div ng-show={{!field.isEnum}}>
        <p ng-show={{field.isRequired}}>{{field.name}}*: </p>
        <p ng-show={{!field.isRequired}}>{{field.name}}: </p>
        <input type="text" ng-model="createChildResource[field.name]"
            class="form-control" style="width:200px; display: inline-block;" placeholder="{{parseClassName(field.type)}}">
    </div>
    <div ng-show={{field.isEnum}}>
        <p ng-show={{field.isRequired}}>{{field.name}}*: </p>
        <p ng-show={{!field.isRequired}}>{{field.name}}: </p>
        <select ng-model="createChildResource[field.name]" class="form-control" style="width:auto; display: inline-block;">
            <option></option>
            <option ng-repeat="enumValue in field.enumValues" label={{enumValue.name}}>{{enumValue.ordinal}}</option>
        </select>
    </div>
</div>
<div class="preview">
    <p>Preview: </p>
    <textarea style="height:350px; width:550px; overflow:scroll;">{{createChildResource | json}}</textarea >
</div>

The output is the following:

enter image description here

But if I try to add a ngModel to a textarea element to be able to edit this values in place like this:

<div class="preview">
    <p>Preview: </p>
    <textarea ng-model="createChildResource" style="height:350px; width:550px; overflow:scroll;">{{createChildResource | json}}</textarea>
</div>

then the output is the following:

enter image description here

In both cases I can't edit my model in a textarea element.

How can this be achieved? I'd like to be able to display and edit my model inplace like in this example with a slight difference: editable-textarea="user.description" should be editable-textarea="user".

7
  • 1
    createChildResource is an object and thus is shown as an object inside the textarea. Try converting the object to a string - JSON.stringify() perhaps and you should see the contents. Commented Apr 1, 2014 at 10:35
  • Yes, now I am able to see the content of an object but still I can't change it. Commented Apr 1, 2014 at 10:41
  • You mean the change is not reflected in the controller or other areas of the view or the textarea is disabled and you can't edit the contents? Commented Apr 1, 2014 at 10:42
  • The view is ok, but If I try to change anything inside textarea - object fields just don't update. Commented Apr 1, 2014 at 10:44
  • Sorry - I did not put it correctly. The object fields do not update inside the textarea or somewhere else? Commented Apr 1, 2014 at 10:45

3 Answers 3

10

I finally understand what you are trying to achieve. On the left you have a bunch of inputs and on the right (bottom), you have a textarea that arranges the inputs as properties of one object and displays them formatted as an object.

Your requirements is to allow user to edit the property values in the textarea and thus update the corresponding property value in the input.

First, as commented, convert the object to a string and then show it inside the textarea.

Next, since you need to react and update the input fields when the textarea is updated, you need to watch the value of the textarea and update the original object (the one that was converted to string).

Using an example here since your code is too complex to understand, let us say that you have the object containerObject as follows:

$scope.containerObject = {
    property_1: "Hello",
    property_2: "World"
};

Your inputs then make use of these properties:

<input ng-model="containerObject.property_1">

<input ng-model="containerObject.property_2">

Now, you wish to display this inside your textarea - you will first convert the object to string and display it as follows:

$scope.getObjectAsText = function () {
    $scope.textAreaModel = JSON.stringify($scope.containerObject);
};

And your textarea markup will look like:

<textarea ng-model="textAreaModel"></textarea>

Each time the value in the input textboxes change, the textarea also gets updated.

Now, for the other way around. When you change the textarea, to have the input textboxes get updated, you need to watch the string model (and NOT the object model):

$scope.$watch('textAreaModel', function () {
    try {
        $scope.containerObject = JSON.parse($scope.textAreaModel);
    } catch(exp) {
        //Exception handler
    };
});

You need to have the try...catch exception handler block because the user may accidentally change the contents such that on converting back to object, the result is not a suitable object (invalid property or invalid syntax).

As long as only the property value is changed, the input values will get updated correctly.

Sign up to request clarification or add additional context in comments.

7 Comments

Yes, you understood target and requirments correctly. It seems to me that this solution will work just fine. Anyway, thank you for your big contribution. It cleared some issues. Your answer is 100% useful. I will inform you as soon as I get some results. Thx :)
Where am I supposed to use function getObjectAsText()?
I guess it should be an ng-change="getObjectAsText()" on every input?
Great, It helped me :) But there's still one issue left: the String in textarea is ugly formatted comparing to the one that was before. Any thoughts how this can be handled? I'll create a separate question if you can help me with this too. This question is solved. Thank you very much :)
My bad - I initially intended to use getObjectAsText() as the model in the view but then changed my mind to use textAreaAsModel instead. You can use it in the ng-change directive or you can set up another $scope.$watch() on the object this time and call getObjectAsText() to update the textarea content each time the input changes.
|
4

You can also wrap it into directive like below, or check the jsfiddler

Html

<div ng-app="app" ng-controller='userCtrl' >
    <textarea obj-edit obj="user" rows='10'></textarea>
    <p ng-bind='user.name'></p>
</div>

javascript

var app = angular.module("app", []);
app.controller('userCtrl', function($scope) {
    $scope.user= {name: 'ron', ocupation: 'coder'};
});
app.directive('objEdit', function() {
    return {
        restrict: 'A',
        scope: {
            obj:'=obj'
        }, 
        link: function(scope, element, attrs) {
            element.text(JSON.stringify(scope.obj, undefined, 2));
            element.change(function(e) {
                console.log(e.currentTarget.value);
                scope.$apply(function() {
                    scope.obj = JSON.parse(e.currentTarget.value);
                });
                console.log(scope.obj);
            })
        }
    }
})

1 Comment

Nice Ron. I went ahead and took it an extra step by using $parsers and $formatters in the standard ngModel codepen
3

Ron's answer is great. Here is code that uses ngModel validation

HTML

<form name="text">
   <textarea obj-edit ng-model="ctrl.json" name="json" rows="25" ng-model-options="{ debounce: 300 }"></textarea>
   <div class="alert alert-danger" role="alert" ng-show="text.json.$error.json">
        Error input
   </div>
</form>

Javascript

app.directive('objEdit', function() {
return {
    restrict: 'A',
    require: "ngModel",
    link: function(scope, element, attrs, ctrl) {
        ctrl.$formatters.push(function formatter(value) {
            return JSON.stringify(value, undefined, 2);
        });
        ctrl.$parsers.push(function(value) {
            try {
                var result = JSON.parse(value);
                ctrl.$setValidity('json', true);
                return result;
            } catch (e) {
                ctrl.$setValidity('json', false);
                return undefined;
            }
        });

    }
}});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.