介紹
當我們要寫一個後台系統的時候,也就兩種方法可以選,一種是全部自己來,一種是拿著別人寫好的樣板來套,今天要來介紹的就是基於angularjs的一套後台系統ng-admin
選擇一個系統當然要看看有沒有什麼好處囉,所以我們來看一下有些什麼功能
- restful api
- CRUD,透過設定的方式來完成
- editor
 其實非常基本的功能,但還是要介紹下去…已經在這個系統上花了不少時間orz
初始
首先要先知道這套系統,基本上不太需要寫太多複雜的程式,只要透過類似設定的方法,就可以做到CRUD,下面來看看該怎麼做
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 
 | angular.module('app', ['ng-admin']).config(['NgAdminConfigurationProvider', function(nga){
 
 
 var admin = nga.application('My First Admin')
 .baseApiUrl('http://jsonplaceholder.typicode.com/');
 
 var user = nga.entity('users');
 
 user.listView().fields([
 nga.field('name'),
 nga.field('username'),
 nga.field('email')
 ])
 
 .listActions(['edit', 'delete']);
 
 user.creationView().fields([
 nga.field('name'),
 nga.field('username'),
 nga.field('email')
 ]);
 
 user.editionView().fields([
 nga.field('username'),
 nga.field('email')
 ]);
 
 admin.addEntity(user);
 nga.configure(admin);
 }]);
 
 | 
前面有說到這是一個引用restful的admin,所以我們要先定義基本的end-point,接著就是定義要顯示的欄位以及真正的資料來源entity,可以透過上面這個範例看到,非常的簡單,我們其實只是用定義欄位的方法就完成了CRUD
end-point
基本上打出去的api會是這個模式,當然這套也提供客製request或是response的功能,在這邊我就不特別提到,需要的可以去翻一下參考文件
| name | type | end-point | 
| listView | GET | /users | 
| creationView | POST | /users | 
| showView | GET | /users/:id | 
| editionView | PUT | /users/:id | 
| deletionView | DELETE | /users/:id | 
欄位資料關連
在admin中很常見到的是要關連另一個資料,例如post中要選擇user,這時候我們可以透過field的關聯欄位來完成
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | var user = nga.entity('users');var post = nga.entity('posts');
 
 post.creationView().fields([
 nga.field('name'),
 
 nga.field('userId', 'reference')
 
 .targetEntity(user)
 
 .targetField(nga.field('username'))
 
 .label('user'),
 nga.field('email'),
 ]);
 
 | 
欄位型態
#客製
基本的功能在參考資料中可以找到很多,其實官方也寫了很多的說明,告訴你要怎麼使用,以下是官方所支援的所有類型,詳細的使用方法就到各個詳細連結中看囉
但你一定會有那種需求是上面都做不到的(我就有一個T_T,那我們要怎麼來自定義這些功能呢
自定欄位型態
首先的首先,就是這邊一定要用es6的寫法了,所以當你看到這邊,趕快把你的專案加上babel的編譯吧!
在這邊我要做的是當另一個欄位值改變時,要將下拉選單的清單更新,這邊我會使用reference作為基底去進行實作一個叫做relate的型態
這邊總共會寫三個檔案
- relateField.js,定義可以在- nga.field()後面接續的欄位名稱
 - 
- 就是這裡必須要用es6的class寫法 
 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | import ReferenceField from 'admin-config/lib/Field/ReferenceField';class RelateField extends ReferenceField {
 constructor(name) {
 super(name);
 
 this._type = "relate";
 this._referenceField = null;
 }
 
 referenceField(field) {
 if (!arguments.length) return this._referenceField;
 this._referenceField = field;
 
 return this;
 }
 }
 
 export default RelateField;
 
 
 | 
- relateFieldView.js,定義在各種情況下要怎麼呈現,我只在write的情況下改成自己的directive
| 12
 3
 4
 5
 6
 
 | export default {getReadWidget: () => '<ma-reference-column field="::field" value="::value" datastore="::datastore"></ma-reference-column>',
 getLinkWidget: () => '<ma-reference-link-column entry="::entry" field="::field" value="::value" datastore="::datastore"></ma-reference-link-column>',
 getFilterWidget: () => '<ma-reference-field field="::field" value="value" datastore="::datastore"></ma-reference-field>',
 getWriteWidget: () => '<relate field="::field" value="value" datastore="::datastore" entry="::entry"></relate>',
 };
 
 | 
- relate.directive.js,定義directive的行為
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 
 | function relate (ReferenceRefresher, locale) {return {
 scope: {
 'field': '&',
 'value': '=',
 'entry': '=?',
 'datastore': '&?',
 },
 restrict: 'E',
 template: '<ma-choice-field field="field()" datastore="datastore()" refresh="refresh($search)"' +
 ' value="value"></ma-choice-field>',
 link: function (scope) {
 const field = scope.field();
 const referenceFieldId = field.referenceField().name();
 scope.name = field.name();
 scope.v = field.validation();
 
 scope.$watch('entry.values.' + referenceFieldId, function (val) {
 if (!val) {
 scope.value = undefined;
 
 return;
 }
 
 const search = {
 [referenceFieldId]: scope.entry.values[referenceFieldId],
 };
 
 
 ReferenceRefresher.refresh(field, scope.value, search)
 .then(function (formattedResults) {
 scope.$broadcast('choices:update', { choices: formattedResults });
 });
 });
 },
 };
 }
 
 relate.$inject = ['ReferenceRefresher', 'locale'];
 
 export default relate;
 
 | 
都寫好以上的程式以後,只要在一開始的config宣告就可以使用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | angular.module('app', ['ng-admin']).config(['NgAdminConfigurationProvider', 'FieldViewConfigurationProvider',
 function(nga, fvp){
 nga.registerFieldType('relate', RelateField);
 fvp.registerFieldView('relate', RelateFieldView);
 
 post.creationView().fields([
 nga.field('name'),
 nga.field('userId', 'relate')
 .targetEntity(user)
 .targetField(nga.field('username'))
 
 .referenceField(nga.field('name'))
 .label('user'),
 ]);
 });
 
 | 
但是這邊稍微實驗一下,可以發現在directive中datastore是無法在一開始拿到users的資料,跟reference的行為有一點點的不一樣,追了一下source code以後,發現這是被包起來定義好的,所以沒辦法有相同的行為,但還好的是我不需要一開始就有資料,而是要等到referenceField變更以後再取得
結論
今天的介紹就到這邊,這套admin是一個蠻不一樣的架構,不希望你寫太多程式,而是透過設定的模版來達到需求,但是當今天功能越來越複雜,這套的限制就會越來越大,再客製上會有很大的侷限,如果已經知道未來會有很多變化(不管是欄位或是api),那建議就是不要使用這套,而是採用其他模版透過寫程式的方式最沒有侷限
參考