Thanks for considering contributions to the ui-grid project. This doc will give you a jump start on the development standards we use.
Grunt task dev will run jshint, compile less, run fontella, run unit tests, run protractor tests, and start a local webserver on port 9003. A watch is started to rerun all the tasks if any source file changes.
``` grunt dev ```
http://localhost:9003/docs/#/tutorial to browse each tutorial.
options
no-e2e - eliminate protractor tests
angular=n.n.n - specify a specify angular version to run unit tests against
core - run only the tests for the core code, skip features
fast - alias for --no-e2e --core --angular=1.3.6
grunt dev --no-e2e --angular=1.3.16
The development goal of ui-grid (ng-grid 3.0) is a fast, testable, and extensible grid component.
The core angular module (ui.grid) provides the basics
- Virtualization
- Row Selection
Everything else should be added as new angular modules unless the grid team agrees that it's a core feature. All new feature modules should be developed as plugins, and be hosted in their own repositories. There is a great blog post about developing a plugin for ui-grid. Your plugin should use the available publicApi, if you need something in the publicApi that isn't currently exposed, we welcome pull requests.
The grid team has limited time to spend on this project, and as the list of features grows, so does the effort required to support those features. In a future release we will be working to move some of the existing features out of the core repository. The basic rule of thumb for any new features is: "If it is possible to implement it as a plugin, it should be a plugin".
- We prefer no 3rd party dependencies other than angular.
- jQuery is only used in Unit Tests
- unit test your code! not that hard. see test/unit for examples. Features will be rejected if the test coverage isn't adequate.
- use ngDoc to document how to use your feature. see examples in existing code.
- no global variables
- public methods and events are registered in grid.api (more on that later)
- design and code the angular way. What do we mean by that? Dependency injection, small directives, emphasis the model, not the DOM, tests!
- feature.js contains an enclosure:
(function () {
'use strict';
var module = angular.module('ui.grid.feature', ['ui.grid']);
})();
- Constants should be added to module.constants. Anytime a value is used in more than one place, consider a constant.
This folder layout is required to work with build tools
src/featureName
/js
/less
/test
All test files must be name.spec.js to get picked up by our grunt tasks
This pattern has been used in several features and seems to work well.
Any magic strings, etc.
module.constant('uiGridFeatureConstants', {
FEATURE_CONSTANT1: 'abc',
featureGroupConstant: {
GROUP_ONE: 'somevalue',
GROUP_TWO: 'a'
},
//available public events; listed here for convenience and IDE's use it for smart completion
publicEvents: {
featureName : {
event1 : function(scope, newRowCol, oldRowCol){},
event2 : function(scope){}
}
}
});
Anything suited to an angular service. So much easier to unit test logic here than logic in a directive or controller.
module.service('uiGridFeatureService', ['uiGridFeatureConstants'
function (uiGridFeatureConstants) {
var service = {
somethingUseful: function () {}
}
return service;
}]);
The main entry point for your feature.
module.directive('uiGridFeature', ['uiGridFeatureService', 'uiGridFeatureConstants',
function (uiGridEditService, uiGridFeatureConstants) {
return {
restrict: 'A',
replace: true,
priority: 0, // this could be tweaked to fire your directive before/after other features
require: 'uiGrid',
scope: false,
compile: function () {
return {
pre: function ($scope, $elm, $attrs, uiGridCtrl, uiGridFeatureService) {
//register your feature cols and row processors with uiGridCtrl.grid
//do anything else you can safely do here
//!! of course, don't stomp on core grid logic or data
//namespace any properties you need on grid
//a good pattern is to have a service initializeGrid function
uiGridFeatureService.initializeGrid(uiGridCtrl.grid);
}
};
}
};
}]);
Any state that your feature needs should be added to the appropriate model and namespaced with your feature name. An InitializeGrid function on your feature service makes a nice pattern to add state to grid
//grid level state
grid.featureName = {};
grid.featureName.someProperty = 'xyz';
//column level state
grid.columns[0].featureName = {};
grid.columns[0].featureName.someProperty = 123;
//row level state
grid.rows[0].featureName = {};
grid.rows[0].featureName.someProperty = 123;
Extend a core directive
module.directive('uiGridCell', ['uiGridFeatureService',
function (uiGridCellNavService) {
return {
priority: -500, // run after default uiGridCell directive
restrict: 'A',
require: '^uiGrid',
scope: false,
link: function ($scope, $elm, $attrs, uiGridCtrl) {
//add whatever dom binding, manipulation,etc that is safe to do and performs well
}
};
}]);
If necessary...
module.directive('uiGridFeatureDirective', ['uiGridFeatureService',
function (uiGridFeatureService) {
return {
link: function ($scope, $elm, $attrs) {
}
};
}]);
Each feature should implement a directive that enables the feature for the ui-grid element. This is the main entry point
of your feature and allows a developer to use multiple grids on a page and include your feature on some grids and not on
others.
<div ui-grid='options' ui-grid-your-feature></div>
Require the uiGrid controller. In the preLink function, register your column and row builders (see below). See ui.grid.edit unit tests on how to easily test your directive
module.directive('uiGridFeature', ['uiGridFeatureService', 'uiGridFeatureConstants',
function (uiGridEditService, uiGridFeatureConstants) {
return {
replace: true,
priority: 0, // this could be tweaked to fire your directive before/after other features
require: 'uiGrid',
scope: false,
compile: function () {
return {
pre: function ($scope, $elm, $attrs, uiGridCtrl) {
uiGridCtrl.grid.api.registerEventsFromObject(uiGridFeatureConstants.publicEvents);
uiGridCtrl.grid.registerColumnBuilder(uiGridFeatureService.featureColumnBuilder);
uiGridCtrl.grid.registerRowBuilder(uiGridFeatureService.featureRowBuilder);
uiGridCtrl.grid.RowsProcessor(uiGridFeatureService.featureRowsProcessor);
//do anything else you can safely do here
//!! of course, don't stomp on core grid logic or data
}
};
}
};
}]);
Any translated strings for you feature should be populated in the language files in src/js/i18n. Namespace the strings by your feature name. en.js should always be populated.
...
avg: 'avg: ',
min: 'min: ',
max: 'max: '
},
//pinning feature translations
pinning: {
pinLeft: 'Pin Left',
pinRight: 'Pin Right',
unpin: 'Unpin'
}
...
The grid calls different processors for rows and cols that you can implement for your feature.
ColumnBuilder functions allow you to add your own properties / functions to each GridCol object. for testing ease, it's best to create a service that returns the function. See ui.grid.edit unit tests on how to easily test your function
module.service('uiGridFeatureService', ['$log', '$q', '$templateCache',
function ($log, $q, $templateCache) {
var service = {
featureColumnBuilder: function (colDef, col, gridOptions) {
//add any promises to an array
var promises = [];
//do something with col
col.featureProp = colDef.featureProp || 'default';
//return all promises (works even if the array is empty)
return $q.all(promises);
}
}
return service;
}]);
//from feature directive pre-link
uiGridCtrl.grid.registerColumnBuilder(uiGridFeatureService.featureColumnBuilder);
RowBuilder functions allow you to add your own properties / functions to each GridRow object. Again, it's best to implement function in a service. See ui.grid.edit unit tests on how to easily test your function
....
featureRowBuilder: function (row, gridOptions) {
//add any promises to an array
var promises = [];
//do something with col
row.featureProp = gridOptions.featureProp || 'default';
//return all promises (works even if the array is empty)
return $q.all(promises);
}
....
//from feature directive pre-link
uiGridCtrl.grid.registerRowBuilder(uiGridFeatureService.featureRowBuilder);
RowsProcessor allows your feature to affect the entire rows collections. Gives you the ability to sort, group, etc. the row.
....
function featureRowsProcessor(renderableRows) {
return rowSorter.sort(this, renderableRows, this.columns);
....
//from feature directive pre-link
uiGridCtrl.grid.RowsProcessor(uiGridFeatureService.featureRowsProcessor);
The grid provides public api via the GridApi object. This object allows you to register your feature's public api methods and events. It guarantees that your events are specific to a grid instance. Internally, angular scope $broadcast and $on are used.
//preferred method is to use a map so ide's can pick up on the signatures
var publicEvents = {
featureName : {
event1 : function(scope, function(newRowCol, oldRowCol)){},
event2 : function(scope, function(){}){}
}
}
});
//from feature directive pre-link
uiGridCtrl.grid.api.registerEventsFromObject(publicEvents);
//more stringy registration
uiGridCtrl.grid.api.registerEvents('featureName', 'eventName');
//raise event
uiGridCtrl.grid.api.featureName.raise.event1(newRowCol, oldRowCol);
//subscribe to event. You must provide a scope object so the listener will be destroyed when scope is destroyed
//function's this variable will be grid.api
uiGridCtrl.grid.api.featureName.on.event1($scope, function(newRowCol, oldRowCol){});
//register methods
var methods = {
featureName : {
methodName1 : function(yourVar1, yourVar2){
//whatever you method needs to do
},
methodName2 : function(var2){
//do something else
}
}
}
uiGridCtrl.grid.api.registerMethodsFromObject(uiGridFeatureConstants.publicEvents);
//way of the string
uiGridCtrl.grid.api.registerMethod('featureName', 'methodName', function(yourVar){//do something});
//external use
$scope.gridOptions.onRegisterApi = function(gridApi){
//subscribe to event
gridApi.feature.on.event1($scope,function(scope, function(newRowCol, oldRowCol)){
var self = this; //grid.api
var msg = 'row selected ' + row.isSelected;
$log.log(msg);
});
//call method
gridApi.featureName.methodName1('abc','123');
};
The next extension point requires some knowledge of the core grid directives. Thanks to a little known feature in angular, you can stack your feature directives before or after one of the core grid directives. Simply use the same directive name and change the priority to a negative number (to execute after) or a positive number (to execute before)
Here's an example of augmenting the uiGridCell directive to add some element to a grid cell
module.directive('uiGridCell', ['uiGridFeatureService',
function (uiGridCellNavService) {
return {
priority: -500, // run after default uiGridCell directive
restrict: 'A',
require: '^uiGrid',
scope: false,
link: function ($scope, $elm, $attrs, uiGridCtrl) {
if ($scope.col.featureProp === 'somevalue' && $scope.row.featureProp === 'somevalue') {
$elm.find('div').attr("someattrib", 0);
}
//add whatever dom binding, manipulation,etc that is safe to do and performs well
}
};
}]);
At the very least, document your main feature directive using jsDoc comment so it's visible in the api docs. Specify all options available to the feature. See other features for jsdocs examples.
Add a tutorial showing how to use your feature. Copy one of the existing misc/tutorial files, change the name at the top and configure it for your feature. 'grunt dev' and your tutorial is available on http://localhost:9003/docs/#/tutorial. Deployment to http://ui-grid.info/ is done automatically when pushed to ui-grid github.
- No tabs
- Indentions are 2 spaces
- Spaces are preferred between args, keywords, blocks function (uiGridCellNavService, $log){ instead of function(uiGridCellNavService,$log){
- jshint rules are enforced. run 'grunt dev --no-e2e' to see if your code passes (the --no-e2e switch turns off end-to-end testing, which can making development slow. You should still run e2e tests before you push commits!)
- Module names should follow Angular's camelcase format, e.g. "resizeColumns", not "resize-columns".
- Use snake-case for class names, not camelCase.
All tests are writtten using Jasmine for assertions and Protractor for the e2e test driver.
You can select specific tests to run using Jasmine's 'focused testing'. In the version of Jasmine that this build system is running you can change describe('...
to ddescribe('...
or it('...
to iit('...
and only thoes tests will be run.
In the most recent version of Jasmine this has changed to fdescribe('...
and fit('...
however at the moment this is not the version we are running.
- Note: Safari 5 does not allow creating dates from strings where the delimiter is a dash, i.e.
new Date('2015-5-23')
will fail. This will cause your tests to work on all browsers but bomb on Safari 5 and you will have a hard time discovering why. Instead, use slashes like so:new Date('2015/5/23')
.
Run these grunt tasks. Look at the grunt-bump module for how to specify a major/minor/patch/pre-release version. This series will bump the version in package.json, update the changelog for that version, then commit the changes and add a new git tag for the version.
Make sure not to include a preceding 'v' in the version name. It will be done automatically by the bump
task.
# Optionally set the version manually: grunt bump-only --setversion=3.0.1-rc.1
grunt bump-only
grunt changelog
grunt bump-commit
Then push the changes to origin/master and Travis will take care of the rest!
git push origin master --tags
NOTE: Nuget must be pushed to manually