The marmelab blog

Ng-admin 0.7: Configurable Menus, Batch Actions, CSV Export, And More

Published on 6 May 2015 by François with tags ng-admin angular-js oss

We continue to improve ng-admin, the AngularJS admin GUI consuming any RESTful API, on a daily basis. Version 0.7, released today, packs many improvements developed over the last two months. Configurable menus, batch actions, CSV export, datetime fields, linked drop-down lists, these are the highlights of this new release. Read on to see what’s new in detail.

If you want to upgrade an existing ng-admin based administration, it should only be a matter of replacing the library by the newest version. A few minor APIs have changed, though: check the Upgrade to 0.7 guide for an in-deep upgrade guide.

ng-admin menus and submenus

Custom Menus & Submenus, New Menu Configuration syntax

Prior to 0.7, you would customize the appearance of an entity in the menu sidebar using entity.menuView() methods.

post.menuView()
  .order(1)
  .icon('<span class="glyphicon glyphicon-file"></span>');

This menuView() is deprecated in 0.7.

By default, the sidebar menu is still built automatically based on the entities added to the application. But to change the menu, you have to manipulate the main Menu instance. Menu is a new class, with a simple structure, allowing to setup menus and submenus (“child” menus), independently of entities. For instance, you can customize the title and icon of a single menu using the following syntax:

admin.addEntity(post)
admin.menu().getChildByTitle('Post')
    .title('Posts')
    .icon('<span class="glyphicon glyphicon-file"></span>');

However, if the default menu doesn’t suit you, it is recommended to build the menu from scratch, as follows:

admin.menu(nga.menu()
  .addChild(nga.menu(post))
  .addChild(nga.menu(comment).title('Comments'))
  .addChild(nga.menu(tag).icon('<span class="glyphicon glyphicon-tags"></span>'))
);

Adding menu children by hand is also the only way to determine the menu order. The Menu class offers icon(), title(), and template() methods to customize how the menu renders.

You can also choose to define a menu element regardless of any entity. In this case, you should define the internal state the menu points to using link(), and the function to determine whether the menu is active based on the current state with active().

admin.menu(nga.menu()
    .addChild(nga.menu()
        .title('Stats')
        .link('/stats')
        .active(function(path) {
            return path.indexOf('/stats') === 0;
        })
    )
);

You can add also second-level menus.

admin.menu(nga.menu()
    .addChild(nga.menu().title('Miscellaneous')
        .addChild(nga.menu().title('Stats').link('/stats'))
    )
);

With a simple syntax, the new Menu object makes it possible to pack complex menus into a good looking, nice feeling sidebar. Our customers already love it, we hope you’ll appreciate it!

Note that the menuView() syntax is still supported in ng-admin 0.7, but will be removed in the next version.

ng-admin batch actions

Batch Actions

Our users have asked us repeatedly for an easy way to delete several entries in a single click. Or to execute a custom action on several entries. We’ve developed this feature, called batch actions, and made it available to every existing list view.

Usage is simple and intuitive: datagrids contain a selection column (an initial column made of checkboxes). Once the user selects lines, a button appears and displays the number of selected entries. A click on this button reveals the list of “batch actions”, i.e. actions that can be performed on a selection of entries. By default, the only batch action available is a batch delete.

To remove the list of checkboxes, simply set an empty batchActions list on the view:

listView.batchActions([])

On the other hand, if you want to add custom batch actions, simply create a directive expecting an array of entries as attribute. The scope contains a selection variable, which holds the current selection:

listView.batchActions(['delete', '<my-custom-directive entries="selection"></my-custom-directive>'])

ng-admin batch export

CSV Export

By popular demand, all list views now contain an button to export the current set of entries as a CSV file. By default, ng-admin uses the fields displayed in the datagrid, but you can choose to export a different set of fields, or with different types. Use listView.exportFields() to set the fields for the CSV export function.

listView.exportFields([
    nga.field('id', 'number'),
    nga.field('author'),
    nga.field('post_id', 'reference')
        .label('Post')
        .map(truncate)
        .targetEntity(post)
        .targetField(nga.field('title').map(truncate))
    nga.field('body', 'wysiwyg')
        .stripTags(true)
]);

The Export button exports the unpaginated list of entries, matching the current filters. This means that if the filtered list has 3 pages of 25 entries, the Export button will download a single CSV file with all 75 entries.

If you don’t want the Export button in a particular list view, simply override the default list of actions:

listView.actions(['batch', 'create']);
// the default is ['batch', 'create', 'export']

Miscellaneous

Field Type Improvements

  • format() specifies the output for number fields using an Excel-like syntax, powered by numeral.js.

      nga.field('price', 'number').format('$0.00')
    
  • sanitize() allows to strip <script> tags from a wysiwyg field, and is enabled by default.

  • stripTags() allows to output a wysiwyg field as raw text. Very useful in list and show views!

  • The new datetime type allows users to set both the date and the time, and is sent to the backend API as a serialized Date object.

If you need to link to a list with a filter already applied, it’s extremely straightforward — thanks to the new <ma-filtered-list> directive. For instance, here is how you would display a small button to link to the list of comments for the current post from an edit view:

<ma-filtered-list-button entity-name="comments" filter="{ post_id: entry.values.id }" size="sm"></ma-filtered-list-button>

Support for Dependent choice Fields

The choice field translates to a drop-down menu. Since the list of values can be a function, you can easily setup values that depend on already selected values - i.e. dependent drop-down lists.

Here is an example with a country field and a city field, where the choices of the city drop-down depend on the selected country.

nga.field('country', 'choice')
    .choices([
      { value: 'FR', label: 'France' },
      { value: 'US', label: 'USA' },
    ]);
var cities = [
    { country: 'FR', value: 'Paris', label: 'Paris' },
    { country: 'FR', value: 'Nancy', label: 'Nancy' },
    { country: 'US', value: 'NY', label: 'New York' },
    { country: 'US', value: 'SF', label: 'San Francisco' }
]
nga.field('city', 'choice')
    .choices(function(entry) {
        return cities.filter(function (city) {
            return city.country === entry.values.country
        });
    });

Tip: When using a function for choice values, if you meet the “Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!”, that’s because the choices() function returns a new array every time. That’s a known AngularJS limitation (see the infinite digest loop documentation).

label Attribute Supported On All Button Directives

It’s not i18n yet, but you can already customize the label of every button directive. For instance, the <ma-list-button> directive links to the list view of a particular entity. If you find the default button label (“List”) boring, you can easily tweak it your way:

'<ma-list-button entry="entry" entity="entity" label="Get to the list!">
</ma-list-button>'

Factory functions are now compulsory

The main change of version 0.6, factory functions, is now compulsory. This means that you cannot use new Field() or new Entity() anymore. You must use nga.field() and nga.entity() instead.

All the documentation has been updated to fit the new syntax.

Conclusion

Version 0.7 brings many other changes, all listed in the Changelog. I would like to thank all the volunteers who contributed to this version, which is a great step towards a 1.0. As always, feel free to give us your feedback by opening an issue in the GitHub tracker!

comments powered by Disqus