Shaun Xu

The Sheep-Pen of the Shaun


News

logo

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.

MVP

My Stats

  • Posts - 122
  • Comments - 542
  • Trackbacks - 0

Tag Cloud


Recent Comments


Recent Posts


Archives


.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.

   1: <!DOCTYPE html>
   2: <html ng-app="MyApp">
   3: <head>
   4:     <link rel="stylesheet" href="jsoneditor.css" />
   5: </head>
   6:  
   7: <body>
   8:     <h1>Hello AngularJS-JSONEditor</h1>
   9:  
  10:     <div ng-controller="MyCtrl">
  11:     <p>
  12:         <div data-ui-json-editor data-ng-model="json"></div>
  13:         <p>
  14:             {{json}}
  15:         </p>
  16:     </p>
  17:     </div>
  18:  
  19:     <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js"></script>
   1:  
   2:     <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js">
   1: </script>
   2:     <script src="jsoneditor.js">
   1: </script>
   2:  
   3:     <script>
   4:         var app = angular.module('MyApp', []);
   5:  
   6:         app.controller('MyCtrl', function($scope) {
   7:  
   8:             $scope.json = {
   9:                 firstName: 'Shaun',
  10:                 lastName: 'Xu',
  11:                 skills: [
  12:                     'C#',
  13:                     'JavaScript'
  14:                 ],
  15:                 roles: [
  16:                     'dev',
  17:                     'speaker'
  18:                 ]
  19:             };
  20:         });
  21:  
  22:         app.directive('uiJsonEditor', [function (jsoneditorOptions) {
  23:             'use strict';
  24:             return {
  25:                 restrict: 'A',
  26:                 scope: {
  27:                     json: '=ngModel'
  28:                 },
  29:                 link: function (scope, elem) {
  30:                     var opts = {
  31:                         change: function () {
  32:                                 if (scope.editor) {
  33:                                     scope.$apply(function () {
  34:                                         scope.json = scope.editor.get();
  35:                                     });
  36:                                 }
  37:                             }
  38:                     };
  39:                     scope.editor = new JSONEditor(elem[0], opts, scope.json || {});
  40:                 }
  41:             };
  42:         }]);
  43:     
</script>
  20: </body>
  21:  
  22: </html>

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

image

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

image

 

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.

image

 

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.

   1: <p>
   2:     <div data-ui-json-editor data-ng-model="json" data-options="options"></div>
   3:     <p>
   4:         {{json}}
   5:     </p>
   6: </p>

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.

image

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.

   1: angular.module('ui.jsoneditor', [])
   2:     .directive('uiJsonEditor', ['jsoneditorOptions', 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 = angular.extend({}, jsoneditorOptions, 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:     }])
  23:     .provider('jsoneditorOptions', function () {
  24:         'use strict';
  25:         this.options = {};
  26:  
  27:         this.$get = function () {
  28:             var opts = this.options;
  29:             return opts;
  30:         };
  31:  
  32:         this.setOptions = function (value) {
  33:             this.options = value;
  34:         };
  35:     });

Then back to the main module we can define default options of JSONEditor in "app.config" function.

   1: app.config(['jsoneditorOptionsProvider', function (jsoneditorOptionsProvider) {
   2:     jsoneditorOptionsProvider.setOptions({
   3:         name: 'root',
   4:         modes: ['text', 'tree']
   5:     });
   6: }]);

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

image

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: };

image

 

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.

The full sample code is as below.

   1: <!DOCTYPE html>
   2: <html ng-app="MyApp">
   3: <head>
   4:     <link rel="stylesheet" href="jsoneditor.css" />
   5: </head>
   6:  
   7: <body>
   8:     <h1>Hello AngularJS-JSONEditor</h1>
   9:  
  10:     <div ng-controller="MyCtrl">
  11:     <p>
  12:         Default Options
  13:     </p>
  14:     <p>
  15:         <div data-ui-json-editor data-ng-model="json"></div>
  16:     </p>
  17:     <p>
  18:         Instance Options
  19:     </p>
  20:     <p>
  21:         <div data-ui-json-editor data-ng-model="json" data-options="options"></div>
  22:     </p>
  23:     <p>
  24:         {{json}}
  25:     </p>
  26:     </div>
  27:  
  28:     <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js"></script>
   1:  
   2:     <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js">
   1: </script>
   2:     <script src="jsoneditor.js">
   1: </script>
   2:  
   3:     <script>
   4:         angular.module('ui.jsoneditor', [])
   5:             .directive('uiJsonEditor', ['jsoneditorOptions', function (jsoneditorOptions) {
   6:                 'use strict';
   7:                 return {
   8:                     restrict: 'A',
   9:                     scope: {
  10:                         json: '=ngModel',
  11:                         options: '=options'
  12:                     },
  13:                     link: function (scope, elem) {
  14:                         var opts = angular.extend({}, jsoneditorOptions, scope.options);
  15:                         opts.change = opts.change || function () {
  16:                                     if (scope.editor) {
  17:                                         scope.$apply(function () {
  18:                                             scope.json = scope.editor.get();
  19:                                         });
  20:                                     }
  21:                                 };
  22:                         scope.editor = new JSONEditor(elem[0], opts, scope.json || {});
  23:                     }
  24:                 };
  25:             }])
  26:             .provider('jsoneditorOptions', function () {
  27:                 'use strict';
  28:                 this.options = {};
  29:  
  30:                 this.$get = function () {
  31:                     var opts = this.options;
  32:                     return opts;
  33:                 };
  34:  
  35:                 this.setOptions = function (value) {
  36:                     this.options = value;
  37:                 };
  38:             });
  39:     
</script>
   1:  
   2:  
   3:     <script>
   4:         var app = angular.module('MyApp', ['ui.jsoneditor']);
   5:  
   6:         app.config(['jsoneditorOptionsProvider', function (jsoneditorOptionsProvider) {
   7:             jsoneditorOptionsProvider.setOptions({
   8:                 name: 'root',
   9:                 modes: ['text', 'tree']
  10:             });
  11:         }]);
  12:  
  13:         app.controller('MyCtrl', function($scope) {
  14:  
  15:             $scope.options = {
  16:                 name: 'this'
  17:             };
  18:  
  19:             $scope.json = {
  20:                 firstName: 'Shaun',
  21:                 lastName: 'Xu',
  22:                 skills: [
  23:                     'C#',
  24:                     'JavaScript'
  25:                 ],
  26:                 roles: [
  27:                     'dev',
  28:                     'speaker'
  29:                 ]
  30:             };
  31:         });
  32:     
</script>
  29: </body>
  30:  
  31: </html>

And you can find the module I created in GitHub.

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.