diff --git a/app/js/services/tooltip.js b/app/js/services/tooltip.js new file mode 100644 index 00000000..0819ecad --- /dev/null +++ b/app/js/services/tooltip.js @@ -0,0 +1,132 @@ +'use strict'; + +var angular = require('angular'); + +var servicesModule = require('./_index.js'); + +function TooltipService() { + var service = {}; + + var render = function(dest, value, d) { + if (angular.isFunction(value)) { + value = value(d); + } + + if (angular.isElement(value)) { + dest.append(value); + } else { + dest.text(value); + } + + return dest; + }; + + var fill = function(dest, element, count) { + for (var i = 0; i < count; i++) { + dest.append(element.clone()); + } + }; + + service.generator = function(content, options) { + options = options || {}; + + return function(d) { + // partially render content first so we can determine the column count + var columns = 0; + var columnOffset = 0; + var partialContent = []; + + var table = angular.element(''); + table.addClass('osh-tooltip'); + if (options.addonClass) { + table.addClass(options.addonClass); + } + + angular.forEach(content, function(row) { + // row can be a function to output colum values (or more functions) + if (angular.isFunction(row)) { + row = row(d); + } + + var values = []; + angular.forEach(row, function(col, i) { + values.push(col); + if (i + 1 > columns) { + columns = i + 1; + } + }); + + partialContent.push(values); + }); + + // build the header, if any + if (options.title || options.header) { + var thead = angular.element(''); + + if (options.colors) { + columnOffset++; + } + + if (options.title) { + var tr = angular.element(''); + + var th = render( + angular.element(''); + fill(tr, angular.element(''); + angular.forEach(partialContent, function(row, rowIndex) { + var tr = angular.element(''); + + if (options.colors && options.colors[rowIndex]) { + var td = angular.element('
').attr('colspan', columns + columnOffset), + options.title, d); + tr.append(th); + thead.append(tr); + } + + if (options.header) { + var tr = angular.element('
'), columnOffset); + angular.forEach(options.header, function(title) { + tr.append(render(angular.element(''), title, d)); + }); + thead.append(tr); + } + + table.append(thead); + } + + // build the body + var tbody = angular.element('
'); + td.addClass('legend-color-guide'); + + var div = angular.element('
'); + div.css('background-color', options.colors[rowIndex]); + td.append(div); + tr.append(td); + + fill(tr, angular.element('
'), columnOffset - 1); + } else { + fill(tr, angular.element(''), columnOffset); + } + + angular.forEach(row, function(col, i) { + var td = render(angular.element(''), col, d); + if (row.length < columns && i == row.length - 1) { + // auto-set colspan for last entry in row + td.attr('colspan', columns - i); + } + + tr.append(td); + }); + + tbody.append(tr); + }); + table.append(tbody); + + return table[0].outerHTML; + }; + }; + + return service; +} + +servicesModule.service('tooltipService', TooltipService); diff --git a/test/unit/services/tooltip_spec.js b/test/unit/services/tooltip_spec.js new file mode 100644 index 00000000..f4946b64 --- /dev/null +++ b/test/unit/services/tooltip_spec.js @@ -0,0 +1,79 @@ +describe('TooltipService', function() { + var $compile; + var tooltipService; + var sampleData; + + beforeEach(function() { + module('app'); + module('app.services'); + + inject(function($injector) { + $compile = $injector.get('$compile'); + tooltipService = $injector.get('tooltipService'); + }); + + sampleData = { + index: 0, + value: 0, + color: 'green', + data: { custom: 123 } + }; + }); + + it('should generate a simple tooltip', function() { + var generator = tooltipService.generator([['Value']]); + var element = angular.element(generator(sampleData))[0]; + + expect(element.classList).toContain('osh-tooltip'); + expect(element.querySelectorAll('tr').length).toEqual(1); + expect(element.querySelectorAll('td').length).toEqual(1); + expect(element.querySelectorAll('th').length).toEqual(0); + }); + + it('should generate a tooltip with a header', function() { + var generator = tooltipService.generator([ + ['Value'] + ], { title: 'test', header: ['column'] }); + + var element = angular.element(generator(sampleData))[0]; + + expect(element.querySelectorAll('tr').length).toEqual(3); + expect(element.querySelectorAll('thead tr').length).toEqual(2); + expect(element.querySelectorAll('thead tr')[0].innerText).toEqual('test'); + expect(element.querySelectorAll('thead tr')[1].innerText).toEqual('column'); + expect(element.querySelector('tbody tr td').innerText).toEqual('Value'); + }); + + it('should generate a tooltip with custom columns', function() { + var generator = tooltipService.generator([ + ['Value', function(d) { return d.value; }], + ['Custom', function(d) { return d.data.custom; }] + ]); + var element = angular.element(generator(sampleData))[0]; + + expect(element.querySelectorAll('tr').length).toEqual(2); + expect(element.querySelectorAll('td').length).toEqual(4); + + var rows = element.querySelectorAll('tbody tr'); + expect(rows[0].children[0].innerText).toEqual('Value'); + expect(rows[0].children[1].innerText).toEqual('0'); + expect(rows[1].children[0].innerText).toEqual('Custom'); + expect(rows[1].children[1].innerText).toEqual('123'); + }); + + it('should generate a tooltip with colors', function() { + var generator = tooltipService.generator([ + ['Value', function(d) { return d.value; }], + ['Custom', function(d) { return d.data.custom; }] + ], { colors: ['red', 'blue'] }); + var element = angular.element(generator(sampleData))[0]; + + expect(element.querySelectorAll('tr').length).toEqual(2); + expect(element.querySelectorAll('td').length).toEqual(6); + + var guides = element.querySelectorAll('td:first-child.legend-color-guide'); + expect(guides.length).toEqual(2); + expect(guides[0].children[0].getAttribute('style')).toEqual('background-color: red;'); + expect(guides[1].children[0].getAttribute('style')).toEqual('background-color: blue;'); + }); +});