(function () { var VisualizationProvider = function () { this.visualizations = {}; this.visualizationTypes = {}; var defaultConfig = { defaultOptions: {}, skipTypes: false, editorTemplate: null } this.registerVisualization = function (config) { var visualization = _.extend({}, defaultConfig, config); // TODO: this is prone to errors; better refactor. if (_.isEmpty(this.visualizations)) { this.defaultVisualization = visualization; } this.visualizations[config.type] = visualization; if (!config.skipTypes) { this.visualizationTypes[config.name] = config.type; } ; }; this.getSwitchTemplate = function (property) { var pattern = /(<[a-zA-Z0-9-]*?)( |>)/ var mergedTemplates = _.reduce(this.visualizations, function (templates, visualization) { if (visualization[property]) { var ngSwitch = '$1 ng-switch-when="' + visualization.type + '" $2'; var template = visualization[property].replace(pattern, ngSwitch); return templates + "\n" + template; } return templates; }, ""); mergedTemplates = '
' + mergedTemplates + "
"; return mergedTemplates; } this.$get = ['$resource', function ($resource) { var Visualization = $resource('/api/visualizations/:id', {id: '@id'}); Visualization.visualizations = this.visualizations; Visualization.visualizationTypes = this.visualizationTypes; Visualization.renderVisualizationsTemplate = this.getSwitchTemplate('renderTemplate'); Visualization.editorTemplate = this.getSwitchTemplate('editorTemplate'); Visualization.defaultVisualization = this.defaultVisualization; return Visualization; }]; }; var VisualizationRenderer = function ($location, Visualization) { return { restrict: 'E', scope: { visualization: '=', queryResult: '=' }, // TODO: using switch here (and in the options editor) might introduce errors and bad // performance wise. It's better to eventually show the correct template based on the // visualization type and not make the browser render all of them. template: '\n' + Visualization.renderVisualizationsTemplate, replace: false, link: function (scope) { scope.select2Options = { width: '50%' }; function readURL() { var searchFilters = angular.fromJson($location.search().filters); if (searchFilters) { _.forEach(scope.filters, function(filter) { var value = searchFilters[filter.friendlyName]; if (value) { filter.current = value; } }); } } function updateURL(filters) { var current = {}; _.each(filters, function(filter) { if (filter.current) { current[filter.friendlyName] = filter.current; } }); var newSearch = angular.extend($location.search(), { filters: angular.toJson(current) }); $location.search(newSearch); } scope.$watch('queryResult && queryResult.getFilters()', function (filters) { if (filters) { scope.filters = filters; if (filters.length) { readURL(); // start watching for changes and update URL scope.$watch('filters', updateURL, true); } } }); } } }; var VisualizationOptionsEditor = function (Visualization) { return { restrict: 'E', template: Visualization.editorTemplate, replace: false } }; var Filters = function () { return { restrict: 'E', templateUrl: '/views/visualizations/filters.html' } } var EditVisualizationForm = function (Events, Visualization, growl) { return { restrict: 'E', templateUrl: '/views/visualizations/edit_visualization.html', replace: true, scope: { query: '=', queryResult: '=', visualization: '=?', openEditor: '=?', onNewSuccess: '=?' }, link: function (scope, element, attrs) { scope.editRawOptions = currentUser.hasPermission('edit_raw_chart'); scope.visTypes = Visualization.visualizationTypes; scope.newVisualization = function () { return { 'type': Visualization.defaultVisualization.type, 'name': Visualization.defaultVisualization.name, 'description': '', 'options': Visualization.defaultVisualization.defaultOptions }; } if (!scope.visualization) { var unwatch = scope.$watch('query.id', function (queryId) { if (queryId) { unwatch(); scope.visualization = scope.newVisualization(); } }); } scope.$watch('visualization.type', function (type, oldType) { // if not edited by user, set name to match type if (type && oldType != type && scope.visualization && !scope.visForm.name.$dirty) { // poor man's titlecase scope.visualization.name = scope.visualization.type[0] + scope.visualization.type.slice(1).toLowerCase(); } }); scope.submit = function () { if (scope.visualization.id) { Events.record(currentUser, "update", "visualization", scope.visualization.id, {'type': scope.visualization.type}); } else { Events.record(currentUser, "create", "visualization", null, {'type': scope.visualization.type}); } scope.visualization.query_id = scope.query.id; Visualization.save(scope.visualization, function success(result) { growl.addSuccessMessage("Visualization saved"); scope.visualization = scope.newVisualization(scope.query); var visIds = _.pluck(scope.query.visualizations, 'id'); var index = visIds.indexOf(result.id); if (index > -1) { scope.query.visualizations[index] = result; } else { // new visualization scope.query.visualizations.push(result); scope.onNewSuccess && scope.onNewSuccess(result); } }, function error() { growl.addErrorMessage("Visualization could not be saved"); }); }; } } }; angular.module('redash.visualization', []) .provider('Visualization', VisualizationProvider) .directive('visualizationRenderer', ['$location', 'Visualization', VisualizationRenderer]) .directive('visualizationOptionsEditor', ['Visualization', VisualizationOptionsEditor]) .directive('filters', Filters) .directive('editVisulatizationForm', ['Events', 'Visualization', 'growl', EditVisualizationForm]) })();