/*global angular*/ /* * This is an example ng-admin configuration for a blog administration composed * of three entities: post, comment, and tag. Reading the code and the comments * will help you understand how a typical ng-admin application works. * * The remote REST API is simulated in the browser, using FakeRest * (https://github.com/marmelab/FakeRest). Look at the JSON responses in the * browser console to see the data used by ng-admin. * * For simplicity's sake, the entire configuration is written in a single file, * but in a real world situation, you would probably split that configuration * into one file per entity. For another example configuration on a larger set * of entities, and using the best development practices, check out the * Posters Galore demo (http://marmelab.com/ng-admin-demo/). */ (function () { "use strict"; var app = angular.module('myApp', ['ng-admin']); // API Mapping app.config(['RestangularProvider', function (RestangularProvider) { // use the custom query parameters function to format the API request correctly RestangularProvider.addFullRequestInterceptor(function(element, operation, what, url, headers, params) { if (operation === 'getList') { // custom pagination params if (params._page) { var start = (params._page - 1) * params._perPage; var end = params._page * params._perPage - 1; params.range = "[" + start + "," + end + "]"; delete params._page; delete params._perPage; } // custom sort params if (params._sortField) { params.sort = '["' + params._sortField + '","' + params._sortDir + '"]'; delete params._sortField; delete params._sortDir; } // custom filters if (params._filters) { params.filter = params._filters; delete params._filters; } } return { params: params }; }); RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response) { if (operation === "getList") { var headers = response.headers(); if (headers['content-range']) { response.totalCount = headers['content-range'].split('/').pop(); } } return data; }); }]); // Admin definition app.config(['NgAdminConfigurationProvider', function (NgAdminConfigurationProvider) { var nga = NgAdminConfigurationProvider; function truncate(value) { if (!value) { return ''; } return value.length > 50 ? value.substr(0, 50) + '...' : value; } var admin = nga.application('ng-admin blog demo') // application main title .debug(false) // debug disabled .baseApiUrl('http://localhost:3000/'); // main API endpoint // define all entities at the top to allow references between them var post = nga.entity('posts'); // the API endpoint for posts will be http://localhost:3000/posts/:id var comment = nga.entity('comments') .baseApiUrl('http://localhost:3000/') // The base API endpoint can be customized by entity .identifier(nga.field('id')); // you can optionally customize the identifier used in the api ('id' by default) var tag = nga.entity('tags'); var subCategories = [ { category: 'tech', label: 'Computers', value: 'computers' }, { category: 'tech', label: 'Gadgets', value: 'gadgets' }, { category: 'lifestyle', label: 'Travel', value: 'travel' }, { category: 'lifestyle', label: 'Fitness', value: 'fitness' } ]; // set the application entities admin .addEntity(post) .addEntity(tag) .addEntity(comment); // customize entities and views /***************************** * post entity customization * *****************************/ post.listView() .title('All posts') // default title is "[Entity_name] list" .description('List of posts with infinite pagination') // description appears under the title .infinitePagination(true) // load pages as the user scrolls .fields([ nga.field('id').label('id'), // The default displayed name is the camelCase field name. label() overrides id nga.field('title'), // the default list field type is "string", and displays as a string nga.field('published_at', 'date'), // Date field type allows date formatting nga.field('average_note', 'float') // Float type also displays decimal digits .cssClasses('hidden-xs'), nga.field('views', 'number') .cssClasses('hidden-xs'), nga.field('backlinks', 'embedded_list') // display list of related comments .label('Links') .map(links => links ? links.length : '') .template('{{ value }}'), nga.field('tags', 'reference_many') // a Reference is a particular type of field that references another entity .targetEntity(tag) // the tag entity is defined later in this file .targetField(nga.field('name')) // the field to be displayed in this list .cssClasses('hidden-xs') .singleApiCall(ids => { return {'id': ids }; }) ]) .filters([ nga.field('category', 'choice').choices([ { label: 'Tech', value: 'tech' }, { label: 'Lifestyle', value: 'lifestyle' } ]).label('Category'), nga.field('subcategory', 'choice').choices(subCategories).label('Subcategory') ]) .listActions(['show', 'edit', 'delete']) .entryCssClasses(function(entry) { // set row class according to entry return (entry.views > 300) ? 'is-popular' : ''; }) .exportFields([ post.listView().fields(), // fields() without arguments returns the list of fields. That way you can reuse fields from another view to avoid repetition nga.field('category', 'choice') // a choice field is rendered as a dropdown in the edition view .choices([ // List the choice as object literals { label: 'Tech', value: 'tech' }, { label: 'Lifestyle', value: 'lifestyle' } ]), nga.field('subcategory', 'choice') .choices(function(entry) { // choices also accepts a function to return a list of choices based on the current entry return subCategories.filter(function (c) { return c.category === entry.values.category; }); }), ]) .exportOptions({ quotes: true, delimiter: ';' }); post.creationView() .fields([ nga.field('title') // the default edit field type is "string", and displays as a text input .attributes({ placeholder: 'the post title' }) // you can add custom attributes, too .validation({ required: true, minlength: 3, maxlength: 100 }), // add validation rules for fields nga.field('teaser', 'text'), // text field type translates to a textarea nga.field('body', 'wysiwyg'), // overriding the type allows rich text editing for the body nga.field('published_at', 'date') // Date field type translates to a datepicker ]); post.editionView() .title('Edit post "{{ entry.values.title }}"') // title() accepts a template string, which has access to the entry .actions(['list', 'show', 'delete']) // choose which buttons appear in the top action bar. Show is disabled by default .fields([ post.creationView().fields(), // fields() without arguments returns the list of fields. That way you can reuse fields from another view to avoid repetition nga.field('category', 'choice') // a choice field is rendered as a dropdown in the edition view .choices([ // List the choice as object literals { label: 'Tech', value: 'tech' }, { label: 'Lifestyle', value: 'lifestyle' } ]), nga.field('subcategory', 'choice') .choices(function(entry) { // choices also accepts a function to return a list of choices based on the current entry return subCategories.filter(function (c) { return c.category === entry.values.category; }); }) .template('', true), nga.field('tags', 'reference_many') // ReferenceMany translates to a select multiple .targetEntity(tag) .targetField(nga.field('name')) .attributes({ placeholder: 'Select some tags...' }) .remoteComplete(true, { refreshDelay: 300 , searchQuery: function(search) { return { q: search }; } }) .singleApiCall(ids => { return {'id': ids }; }) .cssClasses('col-sm-4'), // customize look and feel through CSS classes nga.field('pictures', 'json'), nga.field('views', 'number') .cssClasses('col-sm-4'), nga.field('average_note', 'float') .cssClasses('col-sm-4'), nga.field('backlinks', 'embedded_list') // display embedded list .targetFields([ nga.field('date', 'datetime'), nga.field('url') .cssClasses('col-lg-10') ]) .sortField('date') .sortDir('DESC'), nga.field('comments', 'referenced_list') // display list of related comments .targetEntity(nga.entity('comments')) .targetReferenceField('post_id') .targetFields([ nga.field('id').isDetailLink(true), nga.field('created_at').label('Posted'), nga.field('body').label('Comment') ]) .sortField('created_at') .sortDir('DESC') .listActions(['edit']), nga.field('').label('') .template('') ]); post.showView() // a showView displays one entry in full page - allows to display more data than in a a list .fields([ nga.field('id'), nga.field('title'), nga.field('teaser'), nga.field('body', 'wysiwyg'), nga.field('published_at', 'date'), nga.field('category', 'choice') // a choice field is rendered as a dropdown in the edition view .choices([ // List the choice as object literals { label: 'Tech', value: 'tech' }, { label: 'Lifestyle', value: 'lifestyle' } ]), nga.field('subcategory', 'choice') .choices(subCategories), nga.field('tags', 'reference_many') // ReferenceMany translates to a select multiple .targetEntity(tag) .targetField(nga.field('name')), nga.field('pictures', 'json'), nga.field('views', 'number'), nga.field('average_note', 'float'), nga.field('backlinks', 'embedded_list') // display embedded list .targetFields([ nga.field('date', 'datetime'), nga.field('url') ]) .sortField('date') .sortDir('DESC'), nga.field('comments', 'referenced_list') // display list of related comments .targetEntity(nga.entity('comments')) .targetReferenceField('post_id') .targetFields([ nga.field('id').isDetailLink(true), nga.field('created_at').label('Posted'), nga.field('body').label('Comment') ]) .sortField('created_at') .sortDir('DESC') .listActions(['edit']), nga.field('').label('') .template(''), nga.field('custom_action').label('') .template('') ]); /******************************** * comment entity customization * ********************************/ comment.listView() .title('Comments') .perPage(10) // limit the number of elements displayed per page. Default is 30. .fields([ nga.field('created_at', 'date') .label('Posted'), nga.field('author.name') .label('Author') .cssClasses('hidden-xs'), nga.field('body', 'wysiwyg') .stripTags(true) .map(truncate), nga.field('post_id', 'reference') .label('Post') .targetEntity(post) .targetField(nga.field('title').map(truncate)) .cssClasses('hidden-xs') .singleApiCall(ids => { return {'id': ids }; }) ]) .filters([ nga.field('q') .label('') .pinned(true) .template('
') .transform(v => v && v.toUpperCase()) // transform the entered value before sending it as a query parameter .map(v => v && v.toLowerCase()), // map the query parameter to a displayed value in the filter form nga.field('created_at', 'date') .label('Posted') .attributes({'placeholder': 'Filter by date'}), nga.field('post_id', 'reference') .label('Post') .targetEntity(post) .targetField(nga.field('title')) .remoteComplete(true, { refreshDelay: 200, searchQuery: function(search) { return { q: search }; } }) ]) .listActions(['edit', 'delete']); comment.creationView() .fields([ nga.field('created_at', 'date') .label('Posted') .defaultValue(new Date()), // preset fields in creation view with defaultValue nga.field('author.name') .label('Author'), nga.field('body', 'wysiwyg'), nga.field('post_id', 'reference') .label('Post') .targetEntity(post) .targetField(nga.field('title').map(truncate)) .sortField('title') .sortDir('ASC') .validation({ required: true }) .remoteComplete(true, { refreshDelay: 200, searchQuery: function(search) { return { q: search }; } }) ]); comment.editionView() .fields(comment.creationView().fields()) .fields([ nga.field('').label('') .template('') // template() can take a function or a string ]); comment.deletionView() .title('Deletion confirmation'); // customize the deletion confirmation message /**************************** * tag entity customization * ****************************/ tag.listView() .infinitePagination(false) // by default, the list view uses infinite pagination. Set to false to use regulat pagination .fields([ nga.field('id').label('ID'), nga.field('name'), nga.field('published', 'boolean').cssClasses(function(entry) { // add custom CSS classes to inputs and columns if (entry && entry.values){ if (entry.values.published) { return 'bg-success text-center'; } return 'bg-warning text-center'; } }), nga.field('custom') .label('Upper name') .template('{{ entry.values.name.toUpperCase() }}') .cssClasses('hidden-xs'), nga.field('nb_posts') ]) .filters([ nga.field('published') .label('Not yet published') .template(' ') .defaultValue(false) ]) .batchActions([]) // disable checkbox column and batch delete .listActions(['show', 'edit']); // define custom controller logic for the tag listView tag.listView().prepare(['Restangular', 'entries', function(Restangular, entries) { // fetch the number of posts for each tag return Restangular.allUrl('posts').getList() .then(function(response) { const nbPostsByTag = {}; response.data.forEach(function(post) { post.tags.forEach(function(tagId) { if (!nbPostsByTag[tagId]) { nbPostsByTag[tagId] = 0; } nbPostsByTag[tagId]++; }); }); entries.map(function(tag) { tag.values.nb_posts = nbPostsByTag[tag.identifierValue]; }); }); }]); tag.editionView() .fields([ nga.field('name'), nga.field('published', 'boolean').validation({ required: true // as this boolean is required, ng-admin will use a checkbox instead of a dropdown }) ]); tag.showView() .fields([ nga.field('name'), nga.field('published', 'boolean') ]); // customize header var customHeaderTemplate = '' + ''; admin.header(customHeaderTemplate); // customize menu admin.menu(nga.menu() .addChild(nga.menu(post).icon('')) // customize the entity menu icon .addChild(nga.menu(comment).icon('')) // you can even use utf-8 symbols! .addChild(nga.menu(tag).icon('')) .addChild(nga.menu().title('Other') .addChild(nga.menu().title('Stats').icon('').link('/stats')) ) ); // customize dashboard var customDashboardTemplate = '
' + '
' + 'Welcome to the demo! Fell free to explore and modify the data. We reset it every few minutes.' + '
' + '
' + '
' + '
' + '' + '
' + '
' + '
' + '
' + '
' + '
' + '' + '
' + '
' + '' + '
' + '
' + '
' + '
' + '' + '
' + '
' + '
'; admin.dashboard(nga.dashboard() .addCollection(nga.collection(post) .name('recent_posts') .title('Recent posts') .perPage(5) // limit the panel to the 5 latest posts .fields([ nga.field('published_at', 'date').label('Published').format('MMM d'), nga.field('title').isDetailLink(true).map(truncate), nga.field('views', 'number') ]) .sortField('published_at') .sortDir('DESC') .order(1) ) .addCollection(nga.collection(post) .name('popular_posts') .title('Popular posts') .perPage(5) // limit the panel to the 5 latest posts .fields([ nga.field('published_at', 'date').label('Published').format('MMM d'), nga.field('title').isDetailLink(true).map(truncate), nga.field('views', 'number') ]) .sortField('views') .sortDir('DESC') .order(3) ) .addCollection(nga.collection(comment) .title('Last comments') .perPage(10) .fields([ nga.field('created_at', 'date') .label('Posted'), nga.field('body', 'wysiwyg') .label('Comment') .stripTags(true) .map(truncate) .isDetailLink(true), nga.field('post_id', 'reference') .label('Post') .targetEntity(post) .targetField(nga.field('title').map(truncate)) ]) .sortField('created_at') .sortDir('DESC') .order(2) ) .addCollection(nga.collection(tag) .title('Tags publication status') .perPage(10) .fields([ nga.field('name'), nga.field('published', 'boolean').label('Is published ?') ]) .listActions(['show']) .order(4) ) .template(customDashboardTemplate) ); nga.configure(admin); }]); app.directive('postLink', ['$location', function ($location) { return { restrict: 'E', scope: { entry: '&' }, template: '

View post

', link: function (scope) { scope.displayPost = function () { $location.path('/posts/show/' + scope.entry().values.post_id); // jshint ignore:line }; } }; }]); app.directive('sendEmail', ['$location', function ($location) { return { restrict: 'E', scope: { post: '&' }, template: 'Send post by email', link: function (scope) { scope.send = function () { $location.path('/sendPost/' + scope.post().values.id); }; } }; }]); // custom 'send post by email' page function sendPostController($stateParams, notification) { this.postId = $stateParams.id; // notification is the service used to display notifications on the top of the screen this.notification = notification; } sendPostController.prototype.sendEmail = function() { if (this.email) { this.notification.log('Email successfully sent to ' + this.email, {addnCls: 'humane-flatty-success'}); } else { this.notification.log('Email is undefined', {addnCls: 'humane-flatty-error'}); } }; sendPostController.$inject = ['$stateParams', 'notification']; var sendPostControllerTemplate = '
' + '' + '' + '
' + '
' + '
' + '
Send
' + '
'; app.config(['$stateProvider', function ($stateProvider) { $stateProvider.state('send-post', { parent: 'ng-admin', url: '/sendPost/:id', params: { id: null }, controller: sendPostController, controllerAs: 'controller', template: sendPostControllerTemplate }); }]); // custom page with menu item var customPageTemplate = '
' + '' + '
'; app.config(['$stateProvider', function ($stateProvider) { $stateProvider.state('stats', { parent: 'ng-admin', url: '/stats', template: customPageTemplate }); }]); }());