介紹
當我們要寫一個後台系統的時候,也就兩種方法可以選,一種是全部自己來,一種是拿著別人寫好的樣板來套,今天要來介紹的就是基於angularjs的一套後台系統ng-admin
選擇一個系統當然要看看有沒有什麼好處囉,所以我們來看一下有些什麼功能
- restful api
- CRUD,透過設定的方式來完成
- editor
其實非常基本的功能,但還是要介紹下去…已經在這個系統上花了不少時間orz
初始
首先要先知道這套系統,基本上不太需要寫太多複雜的程式,只要透過類似設定的方法,就可以做到CRUD,下面來看看該怎麼做
1 2 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的關聯欄位來完成
1 2 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寫法
1 2 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
1 2 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的行為
1 2 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宣告就可以使用
1 2 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),那建議就是不要使用這套,而是採用其他模版透過寫程式的方式最沒有侷限
參考