yaat v1.0.7
Yaat - yet another angularjs table
Yaat is a yet another AngularJS table but it uses server-side processing only. It is suitable for systems where live HTML tables are required with column reordering, hiding, data sorting and paging. It is using Bootstrap for its styling so it doesn't try to reinvent the wheel.
Table of Contents
- Install
- Usage
- API
- Events
- Dynamic CSS classes
- Overriding the standard template
- Build
- Django integration
- Development
Install
Simply with npm:
npm install yaat
Or just download the repository and bundle it in your project.
Usage
Yaat uses some 3rd-party libraries. Hope you've already using some of them. These are:
- jQuery
- jQuery-UI*
- Bootstrap
- AngularJS
(* jQuery-UI is only required when the column dropdown is visible. See the
nodropdownoption for more information)Don't forget to include
yaat.min.jsandyaat.cssfiles as well!- Yaat registers the
yaatmodule. Either use it directly as an AngularJS app or create your own module which depends on this. - Use the
<yat>directive to create a pretty table.
API
Declarative
Use the declarative API if the built-in logic (HTTP loading, table rendering, column hiding and ordering, paging) suits your needs. In this case you still have some HTML attributes where you can customize the behaviour of the directive:
apiThere goes your API entry point which will be used to initialize the table. This value is stored in
$scope.$apiand it is also watched so changing it dynamically causes table reload.Yaat sends a single
HTTP POSTwith initialization data (see later). You must always return the following structure:{ "columns": [ { "key": <string>, "value": <string>, "order": <integer>, "hidden": <boolean> }, ... ], "rows": [ { "id": <string> "values": [ <string>, ... ] }, ... ], "pages": { "current": <string>, "list": [ { "key": <string>, "value": <string> }, ... ], } }You must always return every available column name in
columnsfor every request, but you should not return every cell in every row. Use thehiddenproperty for those columns which should be skipped from rendering and also leave those values out from rows."key"indicates column key, which can be anything."value"will be placed on the rendered table.Use the key
"order"to indicate if the column is ordered. Value0stands for unordered,1is for ascending and2is for descending order.Leave
"order"or"hidden"keys out if you don't wish to let the user change their value (their toggle buttons will not be rendered)."rows"contains every result rows.idfield should be unique for each row (so you should use some primary key here).valuesarray contains the actual row cells.pages.listobject contains pages as an array. In it the"key"is used as page offset (passed to$scope.loadPage()) andvalueis rendered on the UI.pages.currentis equal to the"key"of the current page in thepages.listarray. It is rendered non-clickable.
offsetStart page number (or any string). Value is stored in
$scope.$offsetand in$scope.$untouchedOffset(which is always the value used for initialization). On table init the$scope.$untouchedOffsetisPOSTed On paging the$scope.$offsetis sent. Default value:null.limitRequired row count. This value can either be a constant or a model. Default value:
25. Current value is stored in$scope.$limit. This value is watched so you can set the visible row count dynamically.nocontrolsTo hide the.ya-ctrl<div>use this attribute. The value is not stored but if the attribute is exitst the$scope.$noControlsis set totrue. Default value:false.nodropdownUse this attribute if you wish to hide the dropdown. The value of the attribute is not stored but if the attribute exists the
$scope.$noDropdownis set totrue. Default value:false. If this attribute is set ten jQuery-UI is not required.dropdownTextThis is the label text of the show/order drop-down list. Value is stored in
$scope.dropdownTextDefault value:Columns.templateThis is the template url of the
yatdirective which will be used to render the template. Default value:yatable/table.html. SeeOverriding templatessection for more.idThe value of
idattribute is stored in$scope.$yaatId. This is useful for event identification and targeting events to exact table instances.
Note: Declared attributes have higher priority than imperative (scope) ones.
POST
On initialization yaat POSTs the following object:
{
"offset": $scope.$offset,
"limit": $scope.$limit,
"flags": {}
}You have to reply with the header and the row list to this. After the table knows its headers it always sends
their current client-side state in each POST. So the structure after initialization is this:
{
"offset": $scope.$offset,
"limit": $scope.$limit,
"headers": [
{
"order": <integer>,
"hidden": <boolean>,
"key": <string>
}, ...
],
"flags": {}
}You should observe property changes and header differences and reply with the required data. However the table is always rebuilt from scratch meaning that you can deny property changes if you want. The table always reflects the data it have received.
flags object
Since v 1.0.4 yaat sends request flags in its POSTs so the backend can detect which client-side event sent the
POST. The flags object is placed under the "flags" key. It's not a secret that this function was introduced to make
yaat and django-yaat to work together better.
All flags are merged. This simply means that if you call $scope.init() the object will be {init: true}. If you
change the $scope.$api model then the watcher will set the flag api and calls $scope.init() so the POSTed
flags object will be {init: true, api: true}.
The following flags may appear in the POSTs:
- init
- update
- yaat.update
- yaat.reload
- loadPage
- sortable
Note:
initorupdateis always set because they are the core HTTP methods.
Imperative
It is possible to override the default behaviour completely by passing a controller to the
<yat> directive.
<script>
var app = angular.module('yaat');
app.controller('ImperativeExample', ['$scope', function($scope){
// Custom logic comes here
}]);
</script>
<yat ng-controller="ImperativeExample"></yat>Note: Don't add controllers directly to the
yaatmodule.
If you prefer this way but you still want to use the default behaviour just set
the $scope.$api property and everything starts working automagically (details in the
above section).
Hooking
You can override most of scope methods of <yat>'s controller in case you prefer using
your own algorithms but you still want to stick to the original program flow:
$scope.init(url [, flags])This method is called when the value of
$scope.$apiis initialized or changed, the value of$scope.$limitis changed oryaat.initevent is received. New value of$apiis passed asurl.This method adds the
initflag to theflagsobject. You can also add custom flags by passing an object. This object must contain the flag name and the valuetrueassociated to it. Keys with different values are going to be dropped.$scope.update(sortable [, flags])This method is called when any table update is required (hide/sort state or column order changed) or
yaat.updateevent is received. When column order is changed thesortableelement is passed so you can sync the header order (when no ordering made this argument isundefined).This method adds the
updateflag to theflagsobject. You can also add custom flags by passing an object. This object must contain the flag name and the valuetrueassociated to it. Keys with different values are going to be dropped.$scope.loadPage(offset)This method is called when the user navigates through table pages. Offset is the
keyvalue of the clicked$scope.$pagesobject (previous or next).This method adds the
loadPageflag to theflagsobject.
Important: Your methods must operate on
yatmodels or the template fails to render. See the next section for model list.
From scratch
When the $scope.$api value is undefined you get full control over the table rendering.
However there are some variables you must use to hold the values to be rendered:
$scope.$headers: This array contains every available column header. Contained object structure must be this:[ { "key": <string>, "value": <string>, "order": <integer>, "hidden": <boolean> }, ... ]These items will be rendered in the column order/hide box.
Leave
"order"or"hidden"keys out to prevent the column to be ordered or hidden. When the"order"key is missing the"unorderable": truekey will be added to the column. When the"hidden"key is missing the"unhideable": truekey will be added to the column. These are client-side keys only so they will not be sent in anyPOST.$scope.$visibleHeaders: This array contains every visible header. Contained object structure is identical to the ones stored in$headersbut this array must not contain"hidden": trueobjects.These items will be rendered in the table
<thead>section.$scope.$rows: This array contains every result row object. The structure must be this:[ { "id": <string> "values": [ <string>, ... ] }, ... ]Length of each
"values"array should match the length of$scope.$visibleHeadersso every cell have its column in the row.$scope.$pages: This object contains a page array and the key of the current page in that array. Expected structure:{ "current": <string>, "list": [ { "key": <string>, "value": <string> }, ... ], }
Sending and accessing non-Yaat data
It is fine to send custom key-value pairs in the POST replying to Yaat's request. These keys are
going to be collected and stored in $scope.$customData scope variable. The value is populated
at the end of reply parsing meaning that after receiving yaat.http.success you can access
the latest values safely.
Passing sortable(); options
It is possible to customize the behaviour of the sortable which is used for column reordering but you must use the imperative method described above.
$scope.$sortableOptions: Use this property to pass your object.When no update handler is in the object the default handler will be used.
The default behaviour:
{
axis: 'y',
containment: 'parent',
tolerance: 'pointer',
update: function(){
scope.update(this, {sortable: true});
}
}See the widget documentation for more information.
Note:
updatecallback calls the$scope.update()method. Thesortableitself is passed as an argument. Also note that the{sortable: true}flag is passed so this value will appear in theflagsobject.
Mixed mode
It is fine to mix the previous modes. This can be useful when you want to change
the $scope.$sortableOptions but nothing more.
Events
It is possible to send and receive events from Yaat so you can create connections between your own and Yaat's directive. This can be useful when you want the table to be controller by a parent controller (e.g.: Yaat is a child of a filter form so some data of the filter should be passed during paging).
Events that Yaat is listening to
yaat.init(api [, target])This event calls
$scope.init(). An URL must be passed (which will be stored in$scope.$api). You can pass the id of the target table optionally. This event adds theyaat.initflag.yaat.update([target])This event calls
$scope.update()You can pass the id of the target table optionally. This event adds theyaat.updateflag.yaat.reload([target])This event is very similar to
yaat.updateexcept that you should use this after the table is already initialized. When the table receives the event it sends its initial payload again. This is useful for cases where the data is excepted to change. This event adds theyaat.reloadflag.yaat.http.post.add(key, model [, target])You can use this event to add models which should also be sent along with Yaat's own data. This is useful when the table is a child of a parent controller (like a filter form). You can pass the id of the target table optionally.
The keys will be added to the
POSTso you must not use reservedPOSTkeys (offset,limitandheaders). To be sure an error is thrown on conflict.yaat.http.post.remove(key [, target])Use this event to remove a model previously added to be sent along with Yaat's own data. You can pass the id of the target table optinally.
The internal reference of the model is going to be deleted.
Events that Yaat is emitting
yaat.readySent when the
yaatdirective is ready to receive events.yaat.http.successEmitted when the last
POSTand its parsing was successful.yaat.http.errorsThis event is emitted when the
POSTfails. Passed arguments:data(error reply),status(code),headers,config.
Because the sender scope is received in the event object you
can filter event sources by $yaatId.
$scope.$on('yaat.ready', function(e){
if(e.targetScope.$yaatId === 'someTable'){
doSomething();
}
});Dynamic CSS classes
There are some dynamic classes to help customizing the rendered table. These are:
"yh-[[ header.key ]]"Table header cells always have their own
header.keyas class prepended with"yh-". This can be useful for setting column width."yc-[[ getKey($index) ]]"Table body cells always have their column header key (
header.key) as CSS class prepended with"yc-". It is helpful for highlighting a whole column.
Overriding the standard template
It it also possible to override the whole template or just pieces of it. It is very easy using Angular's script based template caching.
Example:
<script type="text/ng-template" id="yatable/row.html">
<!-- Insert template code here -->
</script>yatable/table.htmlThis is the default base template of the rendered table. It includes the control area, renders the table header and rows and includes the paging area.
yatable/controls.htmlThis is the control area of the table. You should place buttons and links which are related to the rendered data here. (e.g.: select all, print, etc). Rendered content is in
.ya-ctrls.yatable/dropdown.htmlThis piece contains the dropdown (which is in the control are). In this list you can reorder the columns, show or hide them and sort their content. The button is pulled to the right.
yatable/row.htmlThis template includes the declaration of the
<tr></tr>element used inside the<tbody></tbody>section of the rendered table. Overriding this template is extremely useful when you want to render a cell value different than others.yatable/paging.htmlThis template includes the paging footer right after the
<table>. It's rendered in.ya-paging.
See
dev/yatable/static/*.htmlfor implementations.
Per-table templates
In case you need more than one instances of <yat> on the same page but one (or many)
should use different templates than others then you can override the template URLs
too.
Available template URLs in the scope:
$controlsTemplateURL of the template of table's control area. Rendered content goes into
.ya-ctrls.Default:
yatable/row.html$rowTemplateURL of the template of
<tr></tr>element used inside the ` section of the rendered table.Default:
yatable/row.html$pagingTemplateURL of the template of table's paging footer. Rendered content goes into
.ya-paging.Default:
yatable/paging.html
Overriding the whole template
You have to pass this URLS as an argument because the directive gets access to its own (and its parents') scope after the template is fetched so it's too later to declare it in the scope.
<yat api="/api/" template="custom-template.html">
Helper methods
You can access all scope methods and objects in your templates listed above of course. However there are some methods which haven't been mentioned yet. They are usually accessed from templates.
$scope.getKey(index)It is just a shortcut of
$scope.$visibleHeaders[index].key.$scope.getIndex(key)Returns the index of the given column key from
$scope.$visibleHeaders.
Build
Node.js is require to build the module:
npm installnpm test
Build result will be placed in dist/ and in dev/yatable/static/js.
Django integration
There is a Django application named django-yaat (built on the top of django-restify-framework) which helps you creating yaat-compatible resources easily.
Development
Source is located in dev/yatable/static/js/yaat.js.
Development server
A simple Django development project can be found in dev/. It is not using django-yaat yet but I'm going to
change this soon. Hope this is not going to affect the yaat directive itself.