<commit_before>(function(){

	var _t = O5._t;

	O5.RoadEvent = Backbone.Model.extend({

		initialize: function() {
			if (!this.has('_visible')) {
				this.set('_visible', true);
			}
		},

		select: function(opts) {
			// valid options:
			// - panTo: asks the map to pan
			// - trigger: send a selection event, which does things like
			// 		display the event details
			opts = _.extend({
				trigger: true
			}, opts || {});
			var self = this;
			_.each(this.collection.where({'_selected': true}), function(rdev) {
				if (rdev !== self) rdev.set('_selected', false);
			});
			this.set('_selected', true);
			if (opts.trigger) O5.app.trigger('selection', this, opts);
		},

		jurisdictionSlug: function() {
			return this.get('jurisdiction_url').replace(/\/$/, '').split('/').slice(-1)[0];
		},

		/**
		 * Like event.get(key), but for Enum fields; returns the friendly,
		 * multilingual name, not the internal key, e.g. "Special event" instead of SPECIAL_EVENT.
		 */
		getEnum: function(key) {
			var val = this.get(key);
			var spec = O5.RoadEventFieldsLookup[key];
			if (!spec || !val || !spec.choices) return val;
			var choice = _.findWhere(spec.choices, {0: val});
			if (!choice) return val;
			return choice[1];
		},

		parse: function(resp) {
			if (resp.meta) {
				delete resp.meta;
			}
			resp.id = resp.url.replace(/\/$/, '').split('/').slice(-2).join('/');
			return resp;
		},

		navigateTo: function(opts) {
			if (this.get('id')) {
				O5.app.router.navigate('events/' + this.get('id'), opts || {});
			}
		},

		url: function() {
			var url = this.get('url');
			if (url) {
				return url;
			}
			return Backbone.Model.prototype.url.call(this);
		},

		update: function(updates, opts) {
			var self = this;
			opts = opts || {};
			$.ajax({
				url: this.url(),
				data: JSON.stringify(updates),
				type: 'POST',
				contentType: 'application/json',
				processData: false,
				success: function() {
					self.fetch();
					if (_.isFunction(opts.success)) {
						opts.success();
					}
				}
			});
		},

		sync: function(method, model, opts) {
			if (method === 'read') {
				// Add some default parameters to the URL
				if (!opts.url) opts.url = model.url();
				opts.url += opts.url.indexOf('?') === -1 ? '?' : '&';
				opts.url += 'format=json&accept-language=' + O5.language;
			}
			else {
				this._sync_called = true;
			}
			return Backbone.sync.call(this, method, model, opts);
		},

		neverSaved: function() {
			// A stronger version of isNew() that returns false
			// as soon as we first try to send the model to the server.
			// Note that even if sending the model to the server
			// fails, this'll continue returning false.
			return this.isNew() && !this._sync_called;
		},

		isChangeInternal: function() {
			return _.every(_.keys(this.changed), function(key) {
				return key.substr(0, 1) === '_';
			});
		}
	});

	// Default Collection.
	O5.RoadEvents = Backbone.Collection.extend({
		model: O5.RoadEvent,

		initialize: function() {
			// Trigger a specific change:except-internal event when a change
			// occurs to non-internal (not underscore-prefixed) fields.
			this.on('change', function(model, opts) {
				if (!model.isChangeInternal()) {
					model.trigger('change:except-internal', model, opts);
				}
			});
		},

		sync: function() {
			throw new Error('Sync not supported on Collection');
		},

		parse: function(resp, xhr) {
			return resp.content;
		}

	});

	O5.RoadEventFields = [
			{
				name: 'headline',
				label: _t('Headline'),
				type: 'text',
				tab: 'basics',
				required: true
			},
			{
				name: 'status',
				label: _t('Status'),
				type: 'enum',
				tab: 'basics',
				choices: [
					['ACTIVE', _t('Active')],
					['ARCHIVED', _t('Archived')]
				],
				'default': 'ACTIVE',
				required: true
			},
			{
				name: 'description',
				label: _t('Description'),
				type: 'text',
				tab: 'details'
			},
			{
				name: 'event_type',
				label: _t('Event type'),
				type: 'enum',
				tab: 'basics',
				choices: [
					['CONSTRUCTION', _t('Planned road work')],
					['SPECIAL_EVENT', _t('Special event')],
					['INCIDENT', _t('Incident (accident, unplanned roadwork...)')]
				],
				'default': 'CONSTRUCTION',
				required: true
			},
			{
				name: 'severity',
				label: _t('Severity'),
				type: 'enum',
				tab: 'basics',
				choices: [
					['1', _t('Minor')],
					['2', _t('Moderate')],
					['3', _t('Major')],
					['9', _t('Undefined')]
				],
				required: true
			},
			{
				name: 'geography',
				label: _t('Geography'),
				type: 'geom',
				widget: 'map',
				tab: 'basics',
				required: true
			},
			{
				name: 'schedule/start_date',
				label: _t('Start date'),
				type: 'date',
				tab: 'schedule',
				required: true
			},
			{
				name: 'schedule/end_date',
				label: _t('End date'),
				type: 'date',
				tab: 'schedule'
			},
			{
				name: 'detour',
				label: _t('Detour'),
				type: 'text',
				tab: 'details'
			},
			{
				name: 'roads',
				label: _t('Roads'),
				type: 'complex',
				widget: 'roads',
				tab: 'roads'
			}
	];

	O5.RoadEventFieldsLookup = {};
	_.each(O5.RoadEventFields, function(f) {
		O5.RoadEventFieldsLookup[f.name] = f;
	});

})();
<commit_msg>The server gives us an explicit event ID now<commit_after>(function(){

	var _t = O5._t;

	O5.RoadEvent = Backbone.Model.extend({

		initialize: function() {
			if (!this.has('_visible')) {
				this.set('_visible', true);
			}
		},

		select: function(opts) {
			// valid options:
			// - panTo: asks the map to pan
			// - trigger: send a selection event, which does things like
			// 		display the event details
			opts = _.extend({
				trigger: true
			}, opts || {});
			var self = this;
			_.each(this.collection.where({'_selected': true}), function(rdev) {
				if (rdev !== self) rdev.set('_selected', false);
			});
			this.set('_selected', true);
			if (opts.trigger) O5.app.trigger('selection', this, opts);
		},

		jurisdictionSlug: function() {
			return this.get('jurisdiction_url').replace(/\/$/, '').split('/').slice(-1)[0];
		},

		/**
		 * Like event.get(key), but for Enum fields; returns the friendly,
		 * multilingual name, not the internal key, e.g. "Special event" instead of SPECIAL_EVENT.
		 */
		getEnum: function(key) {
			var val = this.get(key);
			var spec = O5.RoadEventFieldsLookup[key];
			if (!spec || !val || !spec.choices) return val;
			var choice = _.findWhere(spec.choices, {0: val});
			if (!choice) return val;
			return choice[1];
		},

		parse: function(resp) {
			if (resp.meta) delete resp.meta;
			return resp;
		},

		navigateTo: function(opts) {
			if (this.get('id')) {
				O5.app.router.navigate('events/' + this.get('id'), opts || {});
			}
		},

		url: function() {
			var url = this.get('url');
			if (url) {
				return url;
			}
			return Backbone.Model.prototype.url.call(this);
		},

		update: function(updates, opts) {
			var self = this;
			opts = opts || {};
			$.ajax({
				url: this.url(),
				data: JSON.stringify(updates),
				type: 'POST',
				contentType: 'application/json',
				processData: false,
				success: function() {
					self.fetch();
					if (_.isFunction(opts.success)) {
						opts.success();
					}
				}
			});
		},

		sync: function(method, model, opts) {
			if (method === 'read') {
				// Add some default parameters to the URL
				if (!opts.url) opts.url = model.url();
				opts.url += opts.url.indexOf('?') === -1 ? '?' : '&';
				opts.url += 'format=json&accept-language=' + O5.language;
			}
			else {
				this._sync_called = true;
			}
			return Backbone.sync.call(this, method, model, opts);
		},

		neverSaved: function() {
			// A stronger version of isNew() that returns false
			// as soon as we first try to send the model to the server.
			// Note that even if sending the model to the server
			// fails, this'll continue returning false.
			return this.isNew() && !this._sync_called;
		},

		isChangeInternal: function() {
			return _.every(_.keys(this.changed), function(key) {
				return key.substr(0, 1) === '_';
			});
		}
	});

	// Default Collection.
	O5.RoadEvents = Backbone.Collection.extend({
		model: O5.RoadEvent,

		initialize: function() {
			// Trigger a specific change:except-internal event when a change
			// occurs to non-internal (not underscore-prefixed) fields.
			this.on('change', function(model, opts) {
				if (!model.isChangeInternal()) {
					model.trigger('change:except-internal', model, opts);
				}
			});
		},

		sync: function() {
			throw new Error('Sync not supported on Collection');
		},

		parse: function(resp, xhr) {
			return resp.content;
		}

	});

	O5.RoadEventFields = [
			{
				name: 'headline',
				label: _t('Headline'),
				type: 'text',
				tab: 'basics',
				required: true
			},
			{
				name: 'status',
				label: _t('Status'),
				type: 'enum',
				tab: 'basics',
				choices: [
					['ACTIVE', _t('Active')],
					['ARCHIVED', _t('Archived')]
				],
				'default': 'ACTIVE',
				required: true
			},
			{
				name: 'description',
				label: _t('Description'),
				type: 'text',
				tab: 'details'
			},
			{
				name: 'event_type',
				label: _t('Event type'),
				type: 'enum',
				tab: 'basics',
				choices: [
					['CONSTRUCTION', _t('Planned road work')],
					['SPECIAL_EVENT', _t('Special event')],
					['INCIDENT', _t('Incident (accident, unplanned roadwork...)')]
				],
				'default': 'CONSTRUCTION',
				required: true
			},
			{
				name: 'severity',
				label: _t('Severity'),
				type: 'enum',
				tab: 'basics',
				choices: [
					['1', _t('Minor')],
					['2', _t('Moderate')],
					['3', _t('Major')],
					['9', _t('Undefined')]
				],
				required: true
			},
			{
				name: 'geography',
				label: _t('Geography'),
				type: 'geom',
				widget: 'map',
				tab: 'basics',
				required: true
			},
			{
				name: 'schedule/start_date',
				label: _t('Start date'),
				type: 'date',
				tab: 'schedule',
				required: true
			},
			{
				name: 'schedule/end_date',
				label: _t('End date'),
				type: 'date',
				tab: 'schedule'
			},
			{
				name: 'detour',
				label: _t('Detour'),
				type: 'text',
				tab: 'details'
			},
			{
				name: 'roads',
				label: _t('Roads'),
				type: 'complex',
				widget: 'roads',
				tab: 'roads'
			}
	];

	O5.RoadEventFieldsLookup = {};
	_.each(O5.RoadEventFields, function(f) {
		O5.RoadEventFieldsLookup[f.name] = f;
	});

})();
<|endoftext|>


<commit_before>/*
Copyright [2009-2014] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * RNAcentral metadata search Angular.js app.
 */

; // concatenation safeguard

/**
 * Make it possible to include underscore as a dependency.
 */
var underscore = angular.module('underscore', []);
underscore.factory('_', function() {
    return window._;
});

/**
 * Create RNAcentral app.
 */
var rnaMetasearch = angular.module('rnacentralApp', ['chieffancypants.loadingBar', 'underscore', 'ngAnimate']);

// hide spinning wheel
rnaMetasearch.config(['cfpLoadingBarProvider', function(cfpLoadingBarProvider) {
    cfpLoadingBarProvider.includeSpinner = false;
}]);

/**
 * html5mode removes hashtags from urls.
 */
rnaMetasearch.config(['$locationProvider', function($locationProvider) {
    $locationProvider.html5Mode(true);
}]);

/**
 * Service for launching a metadata search.
 */
rnaMetasearch.service('search', ['$location', function($location) {

    /**
     * To launch a new search, change browser url,
     * which will automatically trigger a new search
     * since the url changes are watched in the query controller.
     */
    this.meta_search = function(query) {
        $location.url('/search' + '?q=' + query);
    };

}]);

/**
 * Service for passing data between controllers.
 */
rnaMetasearch.service('results', ['_', '$http', '$location', '$window', function(_, $http, $location, $window) {

    /**
     * Service initialization.
     */
    var result = {
        hitCount: null,
        entries: [],
        facets: [],
    };

    var status = {
        display_search_interface: false, // hide results section at first
        search_in_progress: false, // display spinning wheel while searching
        show_error: false, // display error message
    };

    var search_config = {
        ebeye_base_url: 'http://wwwdev.ebi.ac.uk/ebisearch/ws/rest/rnacentral',
        rnacentral_base_url: get_base_url(),
        fields: ['description', 'active', 'length'],
        facetfields: ['expert_db', 'rna_type', 'TAXONOMY'], // will be displayed in this order
        facetcount: 30,
        pagesize: 15,
    };

    var query_urls = {
        'ebeye_search': search_config.ebeye_base_url +
                        '?query={QUERY}' +
                        '&format=json' +
                        '&fields=' + search_config.fields.join() +
                        '&facetcount=' + search_config.facetcount +
                        '&facetfields=' + search_config.facetfields.join() +
                        '&size=' + search_config.pagesize +
                        '&start={START}',
        'proxy': search_config.rnacentral_base_url +
                 '/api/internal/ebeye?url={EBEYE_URL}',
    };

    /**
     * Calculate base url for production and development environments.
     */
    function get_base_url() {
        var base_url = $location.protocol() + '://' + $location.host();
        var port = $location.port();
        if (port !== '') {
            base_url += ':' + port
        }
        return base_url;
    }

    /**
     * Launch EBeye search.
     * `start` determines the range of the results to be returned.
     */
    this.search = function(query, start) {
        start = start || 0;

        display_search_interface();
        display_spinner();
        update_page_title();

        query = preprocess_query(query);
        query_url = get_query_url(query, start);
        execute_ebeye_search(query_url, start === 0);

        /**
         * Display search spinner if not a "load more" request.
         */
        function display_spinner() {
            if (start === 0) {
                result.hitCount = null; // display spinner
            }
        }

        /**
         * Change page title, which is also used in browser tabs.
         */
        function update_page_title() {
            $window.document.title = 'Search: ' + query;
        }

        /**
         * Setting `display_search_interface` value to true hides all non-search page content
         * and begins displaying search results interface.
         */
        function display_search_interface() {
            status.display_search_interface = true;
        }

        /**
         * Create an RNAcentral proxy query url which includes EBeye query url.
         */
        function get_query_url() {
            var ebeye_url = query_urls.ebeye_search.replace('{QUERY}', query).replace('{START}', start);
            var url = query_urls.proxy.replace('{EBEYE_URL}', encodeURIComponent(ebeye_url));
            return url;
        }

        /**
         * Split query into words and then:
         *  - append wildcards to all terms without double quotes and not ending with wildcards
         *  - escape special symbols
         *  - capitalize logical operators
         *
         *  Splitting into words is based on this SO question:
         *  http://stackoverflow.com/questions/366202/regex-for-splitting-a-string-using-space-when-not-surrounded-by-single-or-double
         * Each "word" is a sequence of characters that aren't spaces or quotes,
         * or a sequence of characters that begin and end with a quote, with no quotes in between.
         */
        function preprocess_query(query) {
            var words = query.match(/[^\s"]+|"[^"]*"/g);
            var array_length = words.length;
            for (var i = 0; i < array_length; i++) {
                if ( words[i].match(/and|or|not/gi) ) {
                    // capitalize logical operators
                    words[i] = words[i].toUpperCase();
                } else if ( words[i].match(/\:$/gi) ) {
                    // faceted search term, do nothing
                } else if ( words[i].match(/^".+?"$/) ) {
                    // double quotes, do nothing
                } else if ( words[i].match(/\*$/) ) {
                    // wildcard, escape term
                    words[i] = escape_search_term(words[i]);
                } else if ( words[i].match(/\)$/) ) {
                    // right closing grouping parenthesis, don't add a wildcard
                } else {
                    // all other words
                    // escape term, add wildcard
                    words[i] = escape_search_term(words[i]) + '*';
                };
            }
            query = words.join(' ');
            return query;

            /**
             * Escape special symbols used by Lucene
             * Escaped: + - && || ! { } [ ] ^ ~ ? : \
             * Not escaped: * " ( ) because they may be used deliberately by the user
             */
            function escape_search_term(search_term) {
                return search_term.replace(/[\+\-&|!\{\}\[\]\^~\?\:\\]/g, "\\$&");
            }
        }

        /**
         * Execute remote request.
         */
        function execute_ebeye_search(url, overwrite_results) {
            status.search_in_progress = true;
            status.show_error = false;
            $http({
                url: url,
                method: 'GET'
            }).success(function(data) {
                data = preprocess_results(data);
                overwrite_results = overwrite_results || false;
                if (overwrite_results) {
                    result = data; // replace
                } else {
                    // append only entries
                    result.entries = result.entries.concat(data.entries);
                }
                status.search_in_progress = false;
            }).error(function(){
                status.search_in_progress = false;
                status.show_error = true;
            });

            /**
             * Preprocess data received from the server.
             */
            function preprocess_results(data) {
                // sort facets the same way as in config
                _.sortBy(data.facets, function(facet){
                    return _.indexOf(search_config.facetfields, facet.id);
                });
                return data;
            }
        }

    }

    /**
     * Load more results starting from the last loaded index.
     */
    this.load_more_results = function() {
        query = $location.search().q;
        this.search(query, result.entries.length);
    };

    /**
     * Broadcast whether search interface should be displayed.
     */
    this.get_status = function() {
        return status.display_search_interface;
    };

    /**
     * Broadcast search results changes.
     */
    this.get_result = function() {
        return result;
    };

    /**
     * Broadcast whether search is in progress.
     */
    this.get_search_in_progress = function() {
        return status.search_in_progress;
    };

    /**
     * Broadcast whether an error has occurred.
     */
    this.get_show_error = function() {
        return status.show_error;
    };

}]);

rnaMetasearch.controller('MainContent', ['$scope', '$anchorScroll', '$location', 'results', 'search', function($scope, $anchorScroll, $location, results, search) {
    /**
     * Enables scrolling to anchor tags.
     * Title
     */
    $scope.scrollTo = function(id) {
        $location.hash(id);
        $anchorScroll();
    };

    /**
     * Watch `display_search_interface` in order to hide non-search-related content
     * when a search is initiated.
     */
    $scope.$watch(function () { return results.get_status(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.display_search_interface = newValue;
        }
    });

    /**
     * Launch a metadata search from a web page.
     */
    $scope.meta_search = function(query) {
        search.meta_search(query);
    };

}]);

/**
 * Results display controller
 * Responsible for visualising search results.
 */
rnaMetasearch.controller('ResultsListCtrl', ['$scope', '$location', 'results', function($scope, $location, results) {

    $scope.result = {
        entries: [],
    };

    $scope.search_in_progress = results.get_search_in_progress();
    $scope.show_error = results.get_show_error();

    /**
     * Watch `result` changes.
     */
    $scope.$watch(function () { return results.get_result(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.result = newValue;
        }
    });

    /**
     * Watch `display_search_interface` changes.
     */
    $scope.$watch(function () { return results.get_status(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.display_search_interface = newValue;
        }
    });

    /**
     * Watch `search_in_progress` changes.
     */
    $scope.$watch(function () { return results.get_search_in_progress(); }, function (newValue, oldValue) {
        if (newValue != oldValue) {
            $scope.search_in_progress = newValue;
        }
    });

    /**
     * Watch `show_error` changes.
     */
    $scope.$watch(function () { return results.get_show_error(); }, function (newValue, oldValue) {
        if (newValue != oldValue) {
            $scope.show_error = newValue;
        }
    });

    /**
     * Fired when "Load more" button is clicked.
     */
    $scope.load_more_results = function() {
        results.load_more_results();
    };

    /**
     * Determine if the facet has already been applied.
     */
    $scope.is_facet_applied = function(facet_id, facet_value) {
        var query = $location.search().q || '';
        var facet_query = facet_id + ':"' + facet_value + '"';
        if (query.indexOf(facet_query) == -1) {
            return false;
        } else {
            return true;
        }
    };

    /**
     * Run a search with a facet enabled.
     * The facet will be toggled on and off in the repeated calls with the same
     * parameters.
     */
    $scope.facet_search = function(facet_id, facet_value) {
        var query = $location.search().q || '';
        var facet_query = ' AND ' + facet_id + ':"' + facet_value + '"';
        if ($scope.is_facet_applied(facet_id, facet_value)) {
            new_query = query.replace(facet_query, ''); // remove facet
        } else {
            new_query = query + facet_query; // add new facet
        }
        $location.search('q', new_query);
    };

    /**
     * Show/hide search facets to save screen space.
     * Uses jQuery for simplicity.
     * Activated only on mobile devices.
     */
    $scope.toggle_facets = function() {
        var facets = $('.metasearch-facets');
        facets.toggleClass('hidden-xs', !facets.hasClass('hidden-xs'));
        $('#toggle-facets').text(function(i, text){
          return text === "Show facets" ? "Hide facets" : "Show facets";
        });
    };

}]);

/**
 * Query controller
 * Responsible for the search box in the header.
 */
rnaMetasearch.controller('QueryCtrl', ['$scope', '$location', '$window', '$timeout', 'results', 'search', function($scope, $location, $window, $timeout, results, search) {

    $scope.query = {
        text: '',
        submitted: false
    };

    /**
     * Launch a metadata search using the service.
     */
    $scope.meta_search = function(query) {
        search.meta_search(query);
    };

    /**
     * Control browser navigation buttons.
     */
    $scope.$watch(function () { return $location.url(); }, function (newUrl, oldUrl) {
        // ignore url hash
        newUrl = newUrl.replace(/#.+$/, '');
        oldUrl = oldUrl.replace(/#.+$/, '');
        // url has changed
        if (newUrl !== oldUrl) {
            if (newUrl.indexOf('/search') == -1) {
                // a non-search url, load that page
                $timeout(function() {
                    // wrapping in $timeout to avoid "digest in progress" errors
                    $window.location = newUrl;
                });
            } else {
                // the new url is a search result page, launch that search
                $scope.query.text = $location.search().q;
                results.search($location.search().q);
                $scope.query.submitted = false;
            }
        }
    });

    /**
     * Called when the form is submitted.
     */
    $scope.submit_query = function() {
        $scope.query.submitted = true;
        if ($scope.queryForm.text.$invalid) {
            return;
        }
        $scope.meta_search($scope.query.text);
    };

    /**
     * Check if the url contains a query when the controller is first created
     * and initiate a search if necessary.
     */
    (function () {
        if ($location.url().indexOf("/search?q=") > -1) {
            // a search result page, launch a new search
            $scope.query.text = $location.search().q;
            results.search($location.search().q);
        }
    })();

}]);
<commit_msg>Switch to production EBeye endpoint<commit_after>/*
Copyright [2009-2014] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * RNAcentral metadata search Angular.js app.
 */

; // concatenation safeguard

/**
 * Make it possible to include underscore as a dependency.
 */
var underscore = angular.module('underscore', []);
underscore.factory('_', function() {
    return window._;
});

/**
 * Create RNAcentral app.
 */
var rnaMetasearch = angular.module('rnacentralApp', ['chieffancypants.loadingBar', 'underscore', 'ngAnimate']);

// hide spinning wheel
rnaMetasearch.config(['cfpLoadingBarProvider', function(cfpLoadingBarProvider) {
    cfpLoadingBarProvider.includeSpinner = false;
}]);

/**
 * html5mode removes hashtags from urls.
 */
rnaMetasearch.config(['$locationProvider', function($locationProvider) {
    $locationProvider.html5Mode(true);
}]);

/**
 * Service for launching a metadata search.
 */
rnaMetasearch.service('search', ['$location', function($location) {

    /**
     * To launch a new search, change browser url,
     * which will automatically trigger a new search
     * since the url changes are watched in the query controller.
     */
    this.meta_search = function(query) {
        $location.url('/search' + '?q=' + query);
    };

}]);

/**
 * Service for passing data between controllers.
 */
rnaMetasearch.service('results', ['_', '$http', '$location', '$window', function(_, $http, $location, $window) {

    /**
     * Service initialization.
     */
    var result = {
        hitCount: null,
        entries: [],
        facets: [],
    };

    var status = {
        display_search_interface: false, // hide results section at first
        search_in_progress: false, // display spinning wheel while searching
        show_error: false, // display error message
    };

    var search_config = {
        ebeye_base_url: 'http://www.ebi.ac.uk/ebisearch/ws/rest/rnacentral',
        rnacentral_base_url: get_base_url(),
        fields: ['description', 'active', 'length'],
        facetfields: ['expert_db', 'rna_type', 'TAXONOMY'], // will be displayed in this order
        facetcount: 30,
        pagesize: 15,
    };

    var query_urls = {
        'ebeye_search': search_config.ebeye_base_url +
                        '?query={QUERY}' +
                        '&format=json' +
                        '&fields=' + search_config.fields.join() +
                        '&facetcount=' + search_config.facetcount +
                        '&facetfields=' + search_config.facetfields.join() +
                        '&size=' + search_config.pagesize +
                        '&start={START}',
        'proxy': search_config.rnacentral_base_url +
                 '/api/internal/ebeye?url={EBEYE_URL}',
    };

    /**
     * Calculate base url for production and development environments.
     */
    function get_base_url() {
        var base_url = $location.protocol() + '://' + $location.host();
        var port = $location.port();
        if (port !== '') {
            base_url += ':' + port
        }
        return base_url;
    }

    /**
     * Launch EBeye search.
     * `start` determines the range of the results to be returned.
     */
    this.search = function(query, start) {
        start = start || 0;

        display_search_interface();
        display_spinner();
        update_page_title();

        query = preprocess_query(query);
        query_url = get_query_url(query, start);
        execute_ebeye_search(query_url, start === 0);

        /**
         * Display search spinner if not a "load more" request.
         */
        function display_spinner() {
            if (start === 0) {
                result.hitCount = null; // display spinner
            }
        }

        /**
         * Change page title, which is also used in browser tabs.
         */
        function update_page_title() {
            $window.document.title = 'Search: ' + query;
        }

        /**
         * Setting `display_search_interface` value to true hides all non-search page content
         * and begins displaying search results interface.
         */
        function display_search_interface() {
            status.display_search_interface = true;
        }

        /**
         * Create an RNAcentral proxy query url which includes EBeye query url.
         */
        function get_query_url() {
            var ebeye_url = query_urls.ebeye_search.replace('{QUERY}', query).replace('{START}', start);
            var url = query_urls.proxy.replace('{EBEYE_URL}', encodeURIComponent(ebeye_url));
            return url;
        }

        /**
         * Split query into words and then:
         *  - append wildcards to all terms without double quotes and not ending with wildcards
         *  - escape special symbols
         *  - capitalize logical operators
         *
         *  Splitting into words is based on this SO question:
         *  http://stackoverflow.com/questions/366202/regex-for-splitting-a-string-using-space-when-not-surrounded-by-single-or-double
         * Each "word" is a sequence of characters that aren't spaces or quotes,
         * or a sequence of characters that begin and end with a quote, with no quotes in between.
         */
        function preprocess_query(query) {
            var words = query.match(/[^\s"]+|"[^"]*"/g);
            var array_length = words.length;
            for (var i = 0; i < array_length; i++) {
                if ( words[i].match(/and|or|not/gi) ) {
                    // capitalize logical operators
                    words[i] = words[i].toUpperCase();
                } else if ( words[i].match(/\:$/gi) ) {
                    // faceted search term, do nothing
                } else if ( words[i].match(/^".+?"$/) ) {
                    // double quotes, do nothing
                } else if ( words[i].match(/\*$/) ) {
                    // wildcard, escape term
                    words[i] = escape_search_term(words[i]);
                } else if ( words[i].match(/\)$/) ) {
                    // right closing grouping parenthesis, don't add a wildcard
                } else {
                    // all other words
                    // escape term, add wildcard
                    words[i] = escape_search_term(words[i]) + '*';
                };
            }
            query = words.join(' ');
            return query;

            /**
             * Escape special symbols used by Lucene
             * Escaped: + - && || ! { } [ ] ^ ~ ? : \
             * Not escaped: * " ( ) because they may be used deliberately by the user
             */
            function escape_search_term(search_term) {
                return search_term.replace(/[\+\-&|!\{\}\[\]\^~\?\:\\]/g, "\\$&");
            }
        }

        /**
         * Execute remote request.
         */
        function execute_ebeye_search(url, overwrite_results) {
            status.search_in_progress = true;
            status.show_error = false;
            $http({
                url: url,
                method: 'GET'
            }).success(function(data) {
                data = preprocess_results(data);
                overwrite_results = overwrite_results || false;
                if (overwrite_results) {
                    result = data; // replace
                } else {
                    // append only entries
                    result.entries = result.entries.concat(data.entries);
                }
                status.search_in_progress = false;
            }).error(function(){
                status.search_in_progress = false;
                status.show_error = true;
            });

            /**
             * Preprocess data received from the server.
             */
            function preprocess_results(data) {
                // sort facets the same way as in config
                _.sortBy(data.facets, function(facet){
                    return _.indexOf(search_config.facetfields, facet.id);
                });
                return data;
            }
        }

    }

    /**
     * Load more results starting from the last loaded index.
     */
    this.load_more_results = function() {
        query = $location.search().q;
        this.search(query, result.entries.length);
    };

    /**
     * Broadcast whether search interface should be displayed.
     */
    this.get_status = function() {
        return status.display_search_interface;
    };

    /**
     * Broadcast search results changes.
     */
    this.get_result = function() {
        return result;
    };

    /**
     * Broadcast whether search is in progress.
     */
    this.get_search_in_progress = function() {
        return status.search_in_progress;
    };

    /**
     * Broadcast whether an error has occurred.
     */
    this.get_show_error = function() {
        return status.show_error;
    };

}]);

rnaMetasearch.controller('MainContent', ['$scope', '$anchorScroll', '$location', 'results', 'search', function($scope, $anchorScroll, $location, results, search) {
    /**
     * Enables scrolling to anchor tags.
     * Title
     */
    $scope.scrollTo = function(id) {
        $location.hash(id);
        $anchorScroll();
    };

    /**
     * Watch `display_search_interface` in order to hide non-search-related content
     * when a search is initiated.
     */
    $scope.$watch(function () { return results.get_status(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.display_search_interface = newValue;
        }
    });

    /**
     * Launch a metadata search from a web page.
     */
    $scope.meta_search = function(query) {
        search.meta_search(query);
    };

}]);

/**
 * Results display controller
 * Responsible for visualising search results.
 */
rnaMetasearch.controller('ResultsListCtrl', ['$scope', '$location', 'results', function($scope, $location, results) {

    $scope.result = {
        entries: [],
    };

    $scope.search_in_progress = results.get_search_in_progress();
    $scope.show_error = results.get_show_error();

    /**
     * Watch `result` changes.
     */
    $scope.$watch(function () { return results.get_result(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.result = newValue;
        }
    });

    /**
     * Watch `display_search_interface` changes.
     */
    $scope.$watch(function () { return results.get_status(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.display_search_interface = newValue;
        }
    });

    /**
     * Watch `search_in_progress` changes.
     */
    $scope.$watch(function () { return results.get_search_in_progress(); }, function (newValue, oldValue) {
        if (newValue != oldValue) {
            $scope.search_in_progress = newValue;
        }
    });

    /**
     * Watch `show_error` changes.
     */
    $scope.$watch(function () { return results.get_show_error(); }, function (newValue, oldValue) {
        if (newValue != oldValue) {
            $scope.show_error = newValue;
        }
    });

    /**
     * Fired when "Load more" button is clicked.
     */
    $scope.load_more_results = function() {
        results.load_more_results();
    };

    /**
     * Determine if the facet has already been applied.
     */
    $scope.is_facet_applied = function(facet_id, facet_value) {
        var query = $location.search().q || '';
        var facet_query = facet_id + ':"' + facet_value + '"';
        if (query.indexOf(facet_query) == -1) {
            return false;
        } else {
            return true;
        }
    };

    /**
     * Run a search with a facet enabled.
     * The facet will be toggled on and off in the repeated calls with the same
     * parameters.
     */
    $scope.facet_search = function(facet_id, facet_value) {
        var query = $location.search().q || '';
        var facet_query = ' AND ' + facet_id + ':"' + facet_value + '"';
        if ($scope.is_facet_applied(facet_id, facet_value)) {
            new_query = query.replace(facet_query, ''); // remove facet
        } else {
            new_query = query + facet_query; // add new facet
        }
        $location.search('q', new_query);
    };

    /**
     * Show/hide search facets to save screen space.
     * Uses jQuery for simplicity.
     * Activated only on mobile devices.
     */
    $scope.toggle_facets = function() {
        var facets = $('.metasearch-facets');
        facets.toggleClass('hidden-xs', !facets.hasClass('hidden-xs'));
        $('#toggle-facets').text(function(i, text){
          return text === "Show facets" ? "Hide facets" : "Show facets";
        });
    };

}]);

/**
 * Query controller
 * Responsible for the search box in the header.
 */
rnaMetasearch.controller('QueryCtrl', ['$scope', '$location', '$window', '$timeout', 'results', 'search', function($scope, $location, $window, $timeout, results, search) {

    $scope.query = {
        text: '',
        submitted: false
    };

    /**
     * Launch a metadata search using the service.
     */
    $scope.meta_search = function(query) {
        search.meta_search(query);
    };

    /**
     * Control browser navigation buttons.
     */
    $scope.$watch(function () { return $location.url(); }, function (newUrl, oldUrl) {
        // ignore url hash
        newUrl = newUrl.replace(/#.+$/, '');
        oldUrl = oldUrl.replace(/#.+$/, '');
        // url has changed
        if (newUrl !== oldUrl) {
            if (newUrl.indexOf('/search') == -1) {
                // a non-search url, load that page
                $timeout(function() {
                    // wrapping in $timeout to avoid "digest in progress" errors
                    $window.location = newUrl;
                });
            } else {
                // the new url is a search result page, launch that search
                $scope.query.text = $location.search().q;
                results.search($location.search().q);
                $scope.query.submitted = false;
            }
        }
    });

    /**
     * Called when the form is submitted.
     */
    $scope.submit_query = function() {
        $scope.query.submitted = true;
        if ($scope.queryForm.text.$invalid) {
            return;
        }
        $scope.meta_search($scope.query.text);
    };

    /**
     * Check if the url contains a query when the controller is first created
     * and initiate a search if necessary.
     */
    (function () {
        if ($location.url().indexOf("/search?q=") > -1) {
            // a search result page, launch a new search
            $scope.query.text = $location.search().q;
            results.search($location.search().q);
        }
    })();

}]);
<|endoftext|>


<commit_before>var fs = require('fs'),
    Path = require('path'),
    _ = require('underscore'),
    resolvers = require('../resolvers'),
    seq = require('seq'),
    passError = require('passerror'),
    urlTools = require('urltools');

module.exports = function (labelDefinitions, options) {
    options = options || {};
    if (!labelDefinitions) {
        labelDefinitions = [];
    } else if (_.isString(labelDefinitions)) {
        labelDefinitions = [labelDefinitions];
    }
    if (_.isArray(labelDefinitions)) {
        labelDefinitions = labelDefinitions.map(function (labelDefinition) {
            if (_.isString(labelDefinition)) {
                var matchNameTypeAndUrl = labelDefinition.match(/^([^:=]+)(?::([^=]+))?=(.*)$/);
                if (!matchNameTypeAndUrl) {
                    throw new Error("transforms.registerLabelsAsCustomProtocols: Invalid label definition syntax: " + keyEqualsValueStr);
                }
                var type = null;
                labelDefinition = {
                    name: matchNameTypeAndUrl[1],
                    type: matchNameTypeAndUrl[2] || null,
                    url: matchNameTypeAndUrl[3]
                };
            } else if (typeof labelDefinition !== 'object' || labelDefinition === null) {
                throw new Error('transforms.registerLabelsAsCustomProtocols: Invalid label definition: ' + labelDefinition);
            }
            if (!('url' in labelDefinition) || !('name' in labelDefinition)) {
                throw new Error("transforms.registerLabelsAsCustomProtocols: 'name' and 'url' options are mandatory.");
            }
            if (labelDefinition.type && !(labelDefinition.type in resolvers)) {
                throw new Error('transforms.registerLabelsAsCustomProtocols: Unknown resolver type: ' + labelDefinition.type);
            }
            return labelDefinition;
        });
    } else {
        throw new Error("transforms.registerLabelsAsCustomProtocols: Invalid label definitions: " + labelDefinitions);
    }

    return function registerLabelsAsCustomProtocols(assetGraph, cb) {
        if (options.installFindParentDirectoryAsDefault) {
            assetGraph.defaultResolver = resolvers.findParentDirectory();
        }

        seq(labelDefinitions)
            .parEach(function (labelDefinition) {
                var callback = this,
                    resolverName = labelDefinition.type || (/\.jsb\d+$/.test(labelDefinition.url) ? 'senchaJsBuilder' : 'fixedDirectory');

                try {
                    assetGraph.resolverByProtocol[labelDefinition.name] = resolvers[resolverName](urlTools.fsFilePathToFileUrl(labelDefinition.url));
                } catch (err) {
                    err.message = "transforms.registerLabelsAsCustomProtocols: Error initializing resolver: " + err.message;
                    return this(err);
                }
                this();
            })
            .seq(function () {
                cb();
            })
            ['catch'](cb);
    };
};
<commit_msg>lib/transforms/registerLabelsAsCustomProtocols.js: fix jshint errors<commit_after>var _ = require('underscore'),
    resolvers = require('../resolvers'),
    seq = require('seq'),
    urlTools = require('urltools');

module.exports = function (labelDefinitions, options) {
    options = options || {};
    if (!labelDefinitions) {
        labelDefinitions = [];
    } else if (_.isString(labelDefinitions)) {
        labelDefinitions = [labelDefinitions];
    }
    if (_.isArray(labelDefinitions)) {
        labelDefinitions = labelDefinitions.map(function (labelDefinition) {
            if (_.isString(labelDefinition)) {
                var matchNameTypeAndUrl = labelDefinition.match(/^([^:=]+)(?::([^=]+))?=(.*)$/);
                if (!matchNameTypeAndUrl) {
                    throw new Error('transforms.registerLabelsAsCustomProtocols: Invalid label definition syntax: ' + labelDefinition);
                }
                labelDefinition = {
                    name: matchNameTypeAndUrl[1],
                    type: matchNameTypeAndUrl[2] || null,
                    url: matchNameTypeAndUrl[3]
                };
            } else if (typeof labelDefinition !== 'object' || labelDefinition === null) {
                throw new Error('transforms.registerLabelsAsCustomProtocols: Invalid label definition: ' + labelDefinition);
            }
            if (!('url' in labelDefinition) || !('name' in labelDefinition)) {
                throw new Error('transforms.registerLabelsAsCustomProtocols: \'name\' and \'url\' options are mandatory.');
            }
            if (labelDefinition.type && !(labelDefinition.type in resolvers)) {
                throw new Error('transforms.registerLabelsAsCustomProtocols: Unknown resolver type: ' + labelDefinition.type);
            }
            return labelDefinition;
        });
    } else {
        throw new Error('transforms.registerLabelsAsCustomProtocols: Invalid label definitions: ' + labelDefinitions);
    }

    return function registerLabelsAsCustomProtocols(assetGraph, cb) {
        if (options.installFindParentDirectoryAsDefault) {
            assetGraph.defaultResolver = resolvers.findParentDirectory();
        }

        seq(labelDefinitions)
            .parEach(function (labelDefinition) {
                var resolverName = labelDefinition.type || (/\.jsb\d+$/.test(labelDefinition.url) ? 'senchaJsBuilder' : 'fixedDirectory');

                try {
                    assetGraph.resolverByProtocol[labelDefinition.name] = resolvers[resolverName](urlTools.fsFilePathToFileUrl(labelDefinition.url));
                } catch (err) {
                    err.message = 'transforms.registerLabelsAsCustomProtocols: Error initializing resolver: ' + err.message;
                    return this(err);
                }
                this();
            })
            .seq(function () {
                cb();
            })['catch'](cb);
    };
};
<|endoftext|>


<commit_before>/*
Copyright [2009-2014] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// RNAcentral sequence search

;rnaMetasearch.controller('SeqResultsListCtrl', function($scope, $http) {

	$scope.query = {
		sequence: '',
		failed: false
	};
	$scope.results = [];
	$scope.count = 0;

    $scope.submit_query = function() {
    	if (!$scope.seqQueryForm.$valid) {
    		$scope.query.failed = true;
    		return;
    	}
    	$scope.query.failed = false;
        // submit
		$http({
			url: '/api/v1/sequence-search/submit?sequence=' + $scope.query.sequence,
			method: 'GET',
			params: {sequence: $scope.query.sequence}
		}).success(function(data) {
			console.log(data);
			//check status
			var interval = setInterval(function(){
				console.log('checking status');
				$http({
					url: data.url,
					method: 'GET'
				}).success(function(data){
					if (data.status === 'Done') {
						$scope.count = data.count;
						console.log('Results ready');
						window.clearInterval(interval);
						// get results
						$http({
							url: data.url,
							method: 'GET'
						}).success(function(data){
							console.log('Results retrieved');
							console.log(data);
							$scope.results = data.results;
						});
					}
				});
			}, 1000);
		});
    };

});
<commit_msg>Refactor sequence search into multiple functions<commit_after>/*
Copyright [2009-2014] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// RNAcentral sequence search

;rnaMetasearch.controller('SeqResultsListCtrl', function($scope, $http) {

	$scope.query = {
		sequence: '',
		failed: false
	};
	$scope.results = [];
	$scope.count = 0;

    /**
     * Retrive results given a results url.
     */
	var get_results = function(url) {
		$http({
			url: url,
			method: 'GET'
		}).success(function(data){
			console.log('Results retrieved');
			console.log(data);
			$scope.results = data.results;
		}).error(function(){
			// todo
		});
	};

    /**
     * Poll job status in regular intervals.
     */
	var poll_job_status = function(url) {
		var polling_interval = 1000; // milliseconds
		var interval = setInterval(function(){
			console.log('Checking status');
			$http({
				url: url,
				method: 'GET'
			}).success(function(data){
				if (data.status === 'Done') {
					$scope.count = data.count;
					console.log('Results ready');
					window.clearInterval(interval);
					// get results
					get_results(data.url);
				}
			}).error(function(){
				// todo
			});
		}, polling_interval);
	};

    /**
     * Initiate sequence search.
     */
	var search = function(sequence) {
		$http({
			url: '/api/v1/sequence-search/submit?sequence=' + sequence,
			method: 'GET', // todo: switch to POST
		}).success(function(data) {
			console.log(data);
			poll_job_status(data.url);
		}).error(function() {
			// todo
		});
	};

    /**
     * Public method for submitting the query.
     */
    $scope.submit_query = function() {
    	if (!$scope.seqQueryForm.$valid) {
    		$scope.query.failed = true;
    		return;
    	}
    	$scope.query.failed = false;
        search($scope.query.sequence);
    };

});
<|endoftext|>


<commit_before>/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership. Camunda licenses this file to you under the Apache License,
 * Version 2.0; you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';
var fs = require('fs');
var Viewer = require('cmmn-js');

var template = fs.readFileSync(
  __dirname + '/cam-widget-cmmn-viewer.html',
  'utf8'
);

module.exports = [
  '$compile',
  '$location',
  '$rootScope',
  'search',
  'debounce',
  function($compile, $location, $rootScope, search, debounce) {
    return {
      scope: {
        diagramData: '=',
        control: '=?',
        disableNavigation: '&',
        onLoad: '&',
        onClick: '&',
        onMouseEnter: '&',
        onMouseLeave: '&'
      },

      template: template,

      link: function($scope, $element) {
        var definitions;

        $scope.grabbing = false;

        // parse boolean
        $scope.disableNavigation = $scope.$eval($scope.disableNavigation);

        // --- CONTROL FUNCTIONS ---
        $scope.control = $scope.control || {};

        $scope.control.highlight = function(element) {
          $scope.control.addMarker(element, 'highlight');

          var id = element.id || element;
          $element.find('[data-element-id="' + id + '"]>.djs-outline').attr({
            rx: '14px',
            ry: '14px'
          });
        };

        $scope.control.clearHighlight = function(id) {
          $scope.control.removeMarker(id, 'highlight');
        };

        $scope.control.isHighlighted = function(id) {
          return $scope.control.hasMarker(id, 'highlight');
        };

        $scope.control.addMarker = function(element, marker) {
          canvas.addMarker(element, marker);
        };

        $scope.control.removeMarker = function(element, marker) {
          canvas.removeMarker(element, marker);
        };

        $scope.control.hasMarker = function(element, marker) {
          return canvas.hasMarker(element, marker);
        };

        // config: text, tooltip, color, position
        $scope.control.createBadge = function(id, config) {
          var overlays = viewer.get('overlays');

          var htmlElement;
          if (config.html) {
            htmlElement = config.html;
          } else {
            htmlElement = document.createElement('span');
            if (config.color) {
              htmlElement.style['background-color'] = config.color;
            }
            if (config.tooltip) {
              htmlElement.setAttribute('tooltip', config.tooltip);
              htmlElement.setAttribute('tooltip-placement', 'top');
            }
            if (config.text) {
              htmlElement.appendChild(document.createTextNode(config.text));
            }
          }

          var overlayId = overlays.add(id, {
            position: config.position || {
              bottom: 0,
              right: 0
            },
            show: {
              minZoom: -Infinity,
              maxZoom: +Infinity
            },
            html: htmlElement
          });

          $compile(htmlElement)($scope);

          return overlayId;
        };

        // removes all badges for an element with a given id
        $scope.control.removeBadges = function(id) {
          viewer.get('overlays').remove({element: id});
        };

        // removes a single badge with a given id
        $scope.control.removeBadge = function(id) {
          viewer.get('overlays').remove(id);
        };

        $scope.control.getViewer = function() {
          return viewer;
        };

        $scope.control.scrollToElement = function(element) {
          var height, width, x, y;

          var elem = viewer.get('elementRegistry').get(element);
          var viewbox = canvas.viewbox();

          height = Math.max(viewbox.height, elem.height);
          width = Math.max(viewbox.width, elem.width);

          x = Math.min(
            Math.max(viewbox.x, elem.x - viewbox.width + elem.width),
            elem.x
          );
          y = Math.min(
            Math.max(viewbox.y, elem.y - viewbox.height + elem.height),
            elem.y
          );

          canvas.viewbox({
            x: x,
            y: y,
            width: width,
            height: height
          });
        };

        $scope.control.getElement = function(elementId) {
          return viewer.get('elementRegistry').get(elementId);
        };

        $scope.control.getElements = function(filter) {
          return viewer.get('elementRegistry').filter(filter);
        };

        $scope.loaded = false;
        $scope.control.isLoaded = function() {
          return $scope.loaded;
        };

        $scope.control.addAction = function(config) {
          var container = $element.find('.actions');
          var htmlElement = config.html;
          container.append(htmlElement);
          $compile(htmlElement)($scope);
        };

        $scope.control.addImage = function(image, x, y) {
          var addedImage = canvas._viewport.image(image, x, y);
          return addedImage;
        };

        var CmmnViewer = Viewer;
        if ($scope.disableNavigation) {
          CmmnViewer = Object.getPrototypeOf(Viewer.prototype).constructor;
        }
        var viewer = new CmmnViewer({
          container: $element[0].querySelector('.diagram-holder'),
          width: '100%',
          height: '100%',
          canvas: {
            deferUpdate: false
          }
        });

        // The following logic mirrors diagram-js to defer its update of the viewbox change.
        // We tell diagram-js to not defer the update (see above) and do it ourselves instead.
        // Only difference: We use a delay of 0. This causes the update to basically be propagated
        // immediately after the current execution is finished (instead of halting the execution
        // until the viewbox changes and all event listeners are executed). This results in a much
        // better performance while moving the diagram, but at a cost: In the interval between the
        // trigger of the viewbox change and the calculation of the event handlers in the debounced
        // execution, things like badges or migration arrows are at the wrong position; they feel
        // like they are "dragged behind". Therefore, we temporarily hide the overlays.

        // patch show and hide of overlays
        var originalShow = viewer
          .get('overlays')
          .show.bind(viewer.get('overlays'));
        viewer.get('overlays').show = function() {
          viewer.get('eventBus').fire('overlays.show');
          originalShow();
        };

        var originalHide = viewer
          .get('overlays')
          .hide.bind(viewer.get('overlays'));
        viewer.get('overlays').hide = function() {
          viewer.get('eventBus').fire('overlays.hide');
          originalHide();
        };

        var showAgain = debounce(function() {
          viewer.get('overlays').show();
        }, 300);

        var originalViewboxChanged = viewer
          .get('canvas')
          ._viewboxChanged.bind(viewer.get('canvas'));
        var debouncedOriginal = debounce(function() {
          originalViewboxChanged();
          viewer.get('overlays').hide();
          showAgain();
        }, 0);
        viewer.get('canvas')._viewboxChanged = function() {
          debouncedOriginal();
        };

        var diagramData = null;
        var canvas = null;

        $scope.$watch('diagramData', function(newValue) {
          if (newValue) {
            diagramData = newValue;
            renderDiagram();
          }
        });

        function renderDiagram() {
          if (diagramData) {
            $scope.loaded = false;

            var useDefinitions = typeof diagramData === 'object';

            var importFunction = (useDefinitions
              ? viewer.importDefinitions
              : viewer.importXML
            ).bind(viewer);

            importFunction(diagramData, function(err, warn) {
              var applyFunction = useDefinitions
                ? function(fn) {
                    fn();
                  }
                : $scope.$apply.bind($scope);

              applyFunction(function() {
                if (err) {
                  $scope.error = err;
                  return;
                }
                $scope.warn = warn;
                canvas = viewer.get('canvas');
                definitions = viewer._definitions;
                zoom();
                setupEventListeners();
                $scope.loaded = true;
                $scope.onLoad();
              });
            });
          }
        }

        function zoom() {
          if (canvas) {
            var viewbox = JSON.parse(
              ($location.search() || {}).viewbox || '{}'
            )[definitions.id];

            if (viewbox) {
              canvas.viewbox(viewbox);
            } else {
              canvas.zoom('fit-viewport', 'auto');
            }
          }
        }

        var mouseReleaseCallback = function() {
          $scope.grabbing = false;
          document.removeEventListener('mouseup', mouseReleaseCallback);
          $scope.$apply();
        };

        function setupEventListeners() {
          var eventBus = viewer.get('eventBus');
          eventBus.on('element.click', function(e) {
            // e.element = the model element
            // e.gfx = the graphical element
            $scope.onClick({element: e.element, $event: e.originalEvent});
          });
          eventBus.on('element.hover', function(e) {
            $scope.onMouseEnter({element: e.element, $event: e.originalEvent});
          });
          eventBus.on('element.out', function(e) {
            $scope.onMouseLeave({element: e.element, $event: e.originalEvent});
          });
          eventBus.on('element.mousedown', function() {
            $scope.grabbing = true;

            document.addEventListener('mouseup', mouseReleaseCallback);

            $scope.$apply();
          });
          eventBus.on(
            'canvas.viewbox.changed',
            debounce(function(e) {
              var viewbox = JSON.parse(
                ($location.search() || {}).viewbox || '{}'
              );

              viewbox[definitions.id] = {
                x: e.viewbox.x,
                y: e.viewbox.y,
                width: e.viewbox.width,
                height: e.viewbox.height
              };

              search.updateSilently({
                viewbox: JSON.stringify(viewbox)
              });

              var phase = $rootScope.$$phase;
              if (phase !== '$apply' && phase !== '$digest') {
                $scope.$apply(function() {
                  $location.replace();
                });
              } else {
                $location.replace();
              }
            }, 500)
          );
        }

        $scope.zoomIn = function() {
          viewer.get('zoomScroll').zoom(1, {
            x: $element[0].offsetWidth / 2,
            y: $element[0].offsetHeight / 2
          });
        };

        $scope.zoomOut = function() {
          viewer.get('zoomScroll').zoom(-1, {
            x: $element[0].offsetWidth / 2,
            y: $element[0].offsetHeight / 2
          });
        };

        $scope.resetZoom = function() {
          canvas.resized();
          canvas.zoom('fit-viewport', 'auto');
        };
        $scope.control.resetZoom = $scope.resetZoom;

        $scope.control.refreshZoom = function() {
          canvas.resized();
          canvas.zoom(canvas.zoom(), 'auto');
        };
      }
    };
  }
];
<commit_msg>chore(build): use Navigated CMMN viewer<commit_after>/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership. Camunda licenses this file to you under the Apache License,
 * Version 2.0; you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';
var fs = require('fs');
var Viewer = require('cmmn-js/lib/NavigatedViewer');

var template = fs.readFileSync(
  __dirname + '/cam-widget-cmmn-viewer.html',
  'utf8'
);

module.exports = [
  '$compile',
  '$location',
  '$rootScope',
  'search',
  'debounce',
  function($compile, $location, $rootScope, search, debounce) {
    return {
      scope: {
        diagramData: '=',
        control: '=?',
        disableNavigation: '&',
        onLoad: '&',
        onClick: '&',
        onMouseEnter: '&',
        onMouseLeave: '&'
      },

      template: template,

      link: function($scope, $element) {
        var definitions;

        $scope.grabbing = false;

        // parse boolean
        $scope.disableNavigation = $scope.$eval($scope.disableNavigation);

        // --- CONTROL FUNCTIONS ---
        $scope.control = $scope.control || {};

        $scope.control.highlight = function(element) {
          $scope.control.addMarker(element, 'highlight');

          var id = element.id || element;
          $element.find('[data-element-id="' + id + '"]>.djs-outline').attr({
            rx: '14px',
            ry: '14px'
          });
        };

        $scope.control.clearHighlight = function(id) {
          $scope.control.removeMarker(id, 'highlight');
        };

        $scope.control.isHighlighted = function(id) {
          return $scope.control.hasMarker(id, 'highlight');
        };

        $scope.control.addMarker = function(element, marker) {
          canvas.addMarker(element, marker);
        };

        $scope.control.removeMarker = function(element, marker) {
          canvas.removeMarker(element, marker);
        };

        $scope.control.hasMarker = function(element, marker) {
          return canvas.hasMarker(element, marker);
        };

        // config: text, tooltip, color, position
        $scope.control.createBadge = function(id, config) {
          var overlays = viewer.get('overlays');

          var htmlElement;
          if (config.html) {
            htmlElement = config.html;
          } else {
            htmlElement = document.createElement('span');
            if (config.color) {
              htmlElement.style['background-color'] = config.color;
            }
            if (config.tooltip) {
              htmlElement.setAttribute('tooltip', config.tooltip);
              htmlElement.setAttribute('tooltip-placement', 'top');
            }
            if (config.text) {
              htmlElement.appendChild(document.createTextNode(config.text));
            }
          }

          var overlayId = overlays.add(id, {
            position: config.position || {
              bottom: 0,
              right: 0
            },
            show: {
              minZoom: -Infinity,
              maxZoom: +Infinity
            },
            html: htmlElement
          });

          $compile(htmlElement)($scope);

          return overlayId;
        };

        // removes all badges for an element with a given id
        $scope.control.removeBadges = function(id) {
          viewer.get('overlays').remove({element: id});
        };

        // removes a single badge with a given id
        $scope.control.removeBadge = function(id) {
          viewer.get('overlays').remove(id);
        };

        $scope.control.getViewer = function() {
          return viewer;
        };

        $scope.control.scrollToElement = function(element) {
          var height, width, x, y;

          var elem = viewer.get('elementRegistry').get(element);
          var viewbox = canvas.viewbox();

          height = Math.max(viewbox.height, elem.height);
          width = Math.max(viewbox.width, elem.width);

          x = Math.min(
            Math.max(viewbox.x, elem.x - viewbox.width + elem.width),
            elem.x
          );
          y = Math.min(
            Math.max(viewbox.y, elem.y - viewbox.height + elem.height),
            elem.y
          );

          canvas.viewbox({
            x: x,
            y: y,
            width: width,
            height: height
          });
        };

        $scope.control.getElement = function(elementId) {
          return viewer.get('elementRegistry').get(elementId);
        };

        $scope.control.getElements = function(filter) {
          return viewer.get('elementRegistry').filter(filter);
        };

        $scope.loaded = false;
        $scope.control.isLoaded = function() {
          return $scope.loaded;
        };

        $scope.control.addAction = function(config) {
          var container = $element.find('.actions');
          var htmlElement = config.html;
          container.append(htmlElement);
          $compile(htmlElement)($scope);
        };

        $scope.control.addImage = function(image, x, y) {
          var addedImage = canvas._viewport.image(image, x, y);
          return addedImage;
        };

        var CmmnViewer = Viewer;
        if ($scope.disableNavigation) {
          CmmnViewer = Object.getPrototypeOf(Viewer.prototype).constructor;
        }
        var viewer = new CmmnViewer({
          container: $element[0].querySelector('.diagram-holder'),
          width: '100%',
          height: '100%',
          canvas: {
            deferUpdate: false
          }
        });

        // The following logic mirrors diagram-js to defer its update of the viewbox change.
        // We tell diagram-js to not defer the update (see above) and do it ourselves instead.
        // Only difference: We use a delay of 0. This causes the update to basically be propagated
        // immediately after the current execution is finished (instead of halting the execution
        // until the viewbox changes and all event listeners are executed). This results in a much
        // better performance while moving the diagram, but at a cost: In the interval between the
        // trigger of the viewbox change and the calculation of the event handlers in the debounced
        // execution, things like badges or migration arrows are at the wrong position; they feel
        // like they are "dragged behind". Therefore, we temporarily hide the overlays.

        // patch show and hide of overlays
        var originalShow = viewer
          .get('overlays')
          .show.bind(viewer.get('overlays'));
        viewer.get('overlays').show = function() {
          viewer.get('eventBus').fire('overlays.show');
          originalShow();
        };

        var originalHide = viewer
          .get('overlays')
          .hide.bind(viewer.get('overlays'));
        viewer.get('overlays').hide = function() {
          viewer.get('eventBus').fire('overlays.hide');
          originalHide();
        };

        var showAgain = debounce(function() {
          viewer.get('overlays').show();
        }, 300);

        var originalViewboxChanged = viewer
          .get('canvas')
          ._viewboxChanged.bind(viewer.get('canvas'));
        var debouncedOriginal = debounce(function() {
          originalViewboxChanged();
          viewer.get('overlays').hide();
          showAgain();
        }, 0);
        viewer.get('canvas')._viewboxChanged = function() {
          debouncedOriginal();
        };

        var diagramData = null;
        var canvas = null;

        $scope.$watch('diagramData', function(newValue) {
          if (newValue) {
            diagramData = newValue;
            renderDiagram();
          }
        });

        function renderDiagram() {
          if (diagramData) {
            $scope.loaded = false;

            var useDefinitions = typeof diagramData === 'object';

            var importFunction = (useDefinitions
              ? viewer.importDefinitions
              : viewer.importXML
            ).bind(viewer);

            importFunction(diagramData, function(err, warn) {
              var applyFunction = useDefinitions
                ? function(fn) {
                    fn();
                  }
                : $scope.$apply.bind($scope);

              applyFunction(function() {
                if (err) {
                  $scope.error = err;
                  return;
                }
                $scope.warn = warn;
                canvas = viewer.get('canvas');
                definitions = viewer._definitions;
                zoom();
                setupEventListeners();
                $scope.loaded = true;
                $scope.onLoad();
              });
            });
          }
        }

        function zoom() {
          if (canvas) {
            var viewbox = JSON.parse(
              ($location.search() || {}).viewbox || '{}'
            )[definitions.id];

            if (viewbox) {
              canvas.viewbox(viewbox);
            } else {
              canvas.zoom('fit-viewport', 'auto');
            }
          }
        }

        var mouseReleaseCallback = function() {
          $scope.grabbing = false;
          document.removeEventListener('mouseup', mouseReleaseCallback);
          $scope.$apply();
        };

        function setupEventListeners() {
          var eventBus = viewer.get('eventBus');
          eventBus.on('element.click', function(e) {
            // e.element = the model element
            // e.gfx = the graphical element
            $scope.onClick({element: e.element, $event: e.originalEvent});
          });
          eventBus.on('element.hover', function(e) {
            $scope.onMouseEnter({element: e.element, $event: e.originalEvent});
          });
          eventBus.on('element.out', function(e) {
            $scope.onMouseLeave({element: e.element, $event: e.originalEvent});
          });
          eventBus.on('element.mousedown', function() {
            $scope.grabbing = true;

            document.addEventListener('mouseup', mouseReleaseCallback);

            $scope.$apply();
          });
          eventBus.on(
            'canvas.viewbox.changed',
            debounce(function(e) {
              var viewbox = JSON.parse(
                ($location.search() || {}).viewbox || '{}'
              );

              viewbox[definitions.id] = {
                x: e.viewbox.x,
                y: e.viewbox.y,
                width: e.viewbox.width,
                height: e.viewbox.height
              };

              search.updateSilently({
                viewbox: JSON.stringify(viewbox)
              });

              var phase = $rootScope.$$phase;
              if (phase !== '$apply' && phase !== '$digest') {
                $scope.$apply(function() {
                  $location.replace();
                });
              } else {
                $location.replace();
              }
            }, 500)
          );
        }

        $scope.zoomIn = function() {
          viewer.get('zoomScroll').zoom(1, {
            x: $element[0].offsetWidth / 2,
            y: $element[0].offsetHeight / 2
          });
        };

        $scope.zoomOut = function() {
          viewer.get('zoomScroll').zoom(-1, {
            x: $element[0].offsetWidth / 2,
            y: $element[0].offsetHeight / 2
          });
        };

        $scope.resetZoom = function() {
          canvas.resized();
          canvas.zoom('fit-viewport', 'auto');
        };
        $scope.control.resetZoom = $scope.resetZoom;

        $scope.control.refreshZoom = function() {
          canvas.resized();
          canvas.zoom(canvas.zoom(), 'auto');
        };
      }
    };
  }
];
<|endoftext|>