ng-admin Update: Callback Field Type, Easier Customization

Emmanuel Quentin
Emmanuel QuentinSeptember 30, 2014
#ng-admin#angular-js#oss

After our last hackday, we've open-sourced ng-admin, an angular module that provides a GUI to any RESTful API. There was a lot of positive reactions to our work, but we knew that the developer experience wasn't optimal with this first release. During the last week, we've focused on making your life easier when adding ng-admin to existing AngularJS applications.

Bower Package

When we released it, ng-admin was not a real angular module yet, and that made the installation cumbersome. You would have to clone the repository and customize the application directly to use it with your own API.

After a few headaches, the application is now compiled & minified, and can be fetched from bower.

$ bower install --save ng-admin

You can add the ng-admin script as a RequireJS dependency, or simply add it to the HTML source.

<script
  src="/path/to/bower_components/ng-admin/build/ng-admin.min.js"
  type="text/javascript"
></script>

Now that ng-admin is finally a regular module, you can use it in any AngularJS application:

var app = angular.module("myApp", ["ng-admin"]);

Thanks to almond, ngAnnotate and UglifyJS we're now able to compile the whole application with a simple Grunt task.

New Field Types: Rich Text, Custom Type

ng-admin wysiwyg

A simple improvement was to add textAngular, a WYSIWYG Rich Text Editor, to allow the edition of HTML fields:

.addField(new Field('body')
    .label('Body')
    .type('wysiwyg')
    .edition('editable')
)

The new callback field is also available. It allows to return an AngularJS template string. This string can call custom directives, and retrieve scope data:

.addField(new Field('actions')
    .type('callback')
    .list(true)
    .label('My Callback')
    .isEditLink(false)
    .callback(function() {
        // You have access to the current entity
        return '{{ entity.getField("name").value.toUpperCase() }}';
    })
)

A nice trick from Alexis Tondelier was to use an example in the angular doc for $compile and create a directive that compiles the content of an attribute directly in the DOM element:

{% assign valueHtml = '{{ value }}' %}
<div compile="my {{ valueHtml }}"></div>
link: function(scope, element, attrs) {
    scope.$watch(
        function(scope) {
            // watch the 'compile' expression for changes
            return scope.$eval(attrs.compile);
        },
        function(value) {
            // when the 'compile' expression changes assign it into the current DOM
            element.html(value);

            // compile the new DOM and link it to the current scope.
            $compile(element.contents())(scope);
        }
    );
}

Custom directives can now be used in the callback field:

var app = angular.module("myApp", ["ng-admin"]);

app.directive("customPostLink", [
  "$location",
  function($location) {
    return {
      restrict: "E",
      template: '<a ng-click="displayPost(entity)">View&nbsp;post</a>',
      link: function($scope, element, attributes) {
        $scope.displayPost = function(entity) {
          var postId = entity.getField("post_id").value;
          $location.path("/edit/posts/" + postId);
        };
      },
    };
  },
]);

// ...
new Entity("comments").addField(
  new Field("actions")
    .type("callback")
    .list(true)
    .label("Actions")
    .isEditLink(false)
    .callback(function() {
      return "<custom-post-link></custom-post-link>";
    })
);

The callback field can be displayed in the list view, or in the create/edit view.

Quick Filters

ng-admin quick filter

To avoid too many manipulations, you can now define some quick filters that will be displayed above the list view:

.addQuickFilter('Today', function() {
    // Compute current date
    var now = new Date(),
        year = now.getFullYear(),
        month = now.getMonth() + 1,
        day = now.getDate();

        month = month < 10 ? '0' + month : month;
        day = day < 10 ? '0' + day : day;

        return {
            created_at: [year, month, day].join('-')
        }
})

A quickfilter can be an object or a callback that returns an object corresponding to the parameters passed to the API.

Directive All The Things

All ng-admin fields, columns, pagination, grid, and the dashboard element are now rendered as directives. This allows to customize the behaviour or the template of each part of the application.

Here's an example of adding a class to each 'string' field:

var app = angular.module("myApp", ["ng-admin"]);

app.config(function(
  NgAdminConfigurationProvider,
  Application,
  Entity,
  Field,
  Reference,
  ReferencedList,
  ReferenceMany,
  $provide
) {
  // Override input string template
  $provide.decorator("stringFieldDirective", [
    "$delegate",
    function($delegate) {
      // You can modify directly the template
      $delegate[0].template = angular
        .element($delegate[0].template)
        .addClass("MyClass")[0].outerHTML;

      // or use a templateURL (loaded from a file or a <script type="text/ng-template" id="string.html"></script> tag)
      $delegate[0].template = "";
      $delegate[0].templateUrl = "string.html";

      return $delegate;
    },
  ]);

  // ...
});

Next Steps

In future releases, we plan to add more CSS classes to allow UI customization ; we'll also add new widget types for one-to-many relationships in case the related resource has lots of data.

We'll also be introducing the concept of views. A view can store several fields, allowing to display different forms in create / edit mode, and display a show view.

Expect a few breaking changes in the upcoming days while we're progressing towards a stable API.

I hope you'll enjoy ng-admin and these new features. You can test them by yourself in the demo. And feel free to send us feedback on the repository!

Did you like this article? Share it!