The Sheep-Pen of the Shaun

News

Shaun, the author of this blog is a semi-geek, clumsy developer, passionate speaker and incapable architect with about 10 years’ experience in .NET and JavaScript. He hopes to prove that software development is art rather than manufacturing. He's into cloud computing platform and technologies (Windows Azure, Amazon and Aliyun) and right now, Shaun is being attracted by JavaScript (Angular.js and Node.js) and he likes it.

Shaun is working at Worktile Inc. as the chief architect for overall design and develop worktile, a web-based collaboration and task management tool, and lesschat, a real-time communication aggregation tool.

.NET

When I'm developing "My DocumentDB" I decided to enhance the JSON input part by introducing a designer. After Google-ed I found JSONEditor is good to me. It's a web-based tool allows to view, edit and format JSON with simple API to integrate into a web page. Then I was going to use this cool thing into my project.

Use Directive

I firstly created a directive that will apply JSONEditor in its DOM. The JSON data will be specified from the "ng-model" attribute. The code is very simple as below.

1: app.directive('uiJsonEditor', [function (jsoneditorOptions) {

2:'use strict';

3:return {

4: restrict: 'A',

5: scope: {

6: json: '=ngModel'

7: },

8: link: function (scope, elem) {

9:var opts = {

10: change: function () {

11:if (scope.editor) {

12: scope.$apply(function () {

13: scope.json = scope.editor.get();

14: });

15: }

16: }

17: };

18: scope.editor = new JSONEditor(elem[0], opts, scope.json || {});

19: }

20: };

21: }]);

One thing need to be paid attention. I specified JSONEditor "change" event function so that it will update the JSON value back to the scope variant. Since this event triggered outside of AngularJS event-loop I need to wrap the update code into "scope.$apply".

Then I can use JSONEditor in my page. Below is the web page I used for prototype. I attach this directive in a DIV. And I also displayed the scope variant that ensured JSON value was updated accordingly.

After launched the web page JSON data will be shown in both JSONEditor and the text area.

If I changed something in JSONEditor we will find the data was changed automatically.

Use Module

This is good for "My DocumentDB" project, but I was thinking if I can change it as a standalone UI control being used in any my and others' projects. This is not a big deal. In AngularJS we can use module to group controllers, factories, services and directives. In this case what I need to do is to create a module and put the directive into it, and then changed my main module that depends on it.

1:<script>

1:

2: angular.module('ui.jsoneditor', [])

3: .directive('uiJsonEditor', [function (jsoneditorOptions) {

4:'use strict';

5:return {

6: restrict: 'A',

7: scope: {

8: json: '=ngModel'

9: },

10: link: function (scope, elem) {

11:var opts = {

12: change: function () {

13:if (scope.editor) {

14: scope.$apply(function () {

15: scope.json = scope.editor.get();

16: });

17: }

18: }

19: };

20: scope.editor = new JSONEditor(elem[0], opts, scope.json || {});

21: }

22: };

23: }]);

</script>

2:

3:<script>

1:

2:var app = angular.module('MyApp', ['ui.jsoneditor']);

3: app.controller('MyCtrl', function($scope) {

4:

5: $scope.json = {

6: firstName: 'Shaun',

7: lastName: 'Xu',

8: skills: [

9:'C#',

10:'JavaScript'

11: ],

12: roles: [

13:'dev',

14:'speaker'

15: ]

16: };

17: });

</script>

In HTML part I don't need to change anything the page loaded successfully and JSONEditor works well.

Better Configuration

This is better, but not perfect. I knew JSONEditor allows developer specify some options. This can be done by introducing more scope variants into the directive. In the code below I added "options" variant. So we can tell the directive which scope variant will be used as the JSONEditor configuration.

1: angular.module('ui.jsoneditor', [])

2: .directive('uiJsonEditor', [function (jsoneditorOptions) {

3:'use strict';

4:return {

5: restrict: 'A',

6: scope: {

7: json: '=ngModel',

8: options: '=options'

9: },

10: link: function (scope, elem) {

11:var opts = scope.options || {};

12: opts.change = opts.change || function () {

13:if (scope.editor) {

14: scope.$apply(function () {

15: scope.json = scope.editor.get();

16: });

17: }

18: };

19: scope.editor = new JSONEditor(elem[0], opts, scope.json || {});

20: }

21: };

22: }]);

In HTML part I specified which scope variant will be used as the options as below.

And in the controller I specified the options, defined the root node text and modes of JSONEditor.

1:var app = angular.module('MyApp', ['ui.jsoneditor']);

2: app.controller('MyCtrl', function($scope) {

3:

4: $scope.options = {

5: name: 'root',

6: modes: ['tree', 'text']

7: };

8:

9: $scope.json = {

10: firstName: 'Shaun',

11: lastName: 'Xu',

12: skills: [

13:'C#',

14:'JavaScript'

15: ],

16: roles: [

17:'dev',

18:'speaker'

19: ]

20: };

21: });

Refresh the web page we will see the options was changed.

Now it's almost perfect. But if I have more than one JSONEditor controls in my application I might wanted to have a default options. This can be done by using AngularJS provider. In the help page it said

You should use the Provider recipe only when you want to expose an API for application-wide configuration that must be made before the application starts. This is usually interesting only for reusable services whose behavior might need to vary slightly between applications.

Provider holds some variants which can be exposed by a special function named "$get". We can defined functions in it which can be used before the application starts, typically in our AngularJS main module's "config" function.

In our case we just need to create a provider, define a local variant stores JSONEditor options, expose two function. "$get" function will return this variant, "setOptions" function will set options into this value.

And in JSONEditor directive we referenced the provider just created, retrieved the value and merged with the scope variant. Now we have a global options, and developer can specify some options for a particular JSONEditor control as well.

After refresh the web page we will see that the JSONEditor options changed even I had removed the options from controller scope.

And if I specified the options in the controller scope it will be updated, but the global options still remained.

1: $scope.options = {

2: name: 'this'

3: };

Summary

In this post I introduced how to create an AngularJS module wraps a UI control. In AngularJS we should use directive when dealing with DOM. Then I moved the code into a standalone module to make it useable for any other projects. At the end I added the functionality for global configuration.