/static/js/controllers/searchcontroller.js
JavaScript | 321 lines | 185 code | 54 blank | 82 comment | 7 complexity | d2d9f0e8b966953aaca3c6c96f6dc125 MD5 | raw file
Possible License(s): Apache-2.0
- define([
- 'config',
- 'config/searchProviders',
- 'underscore',
- 'backbone',
- 'lib/template',
- 'lib/routecontroller',
- 'models/searchcollections',
- 'views/stackheadinglistview',
- 'views/postlistview',
- 'lib/bridge',
- 'views/widgetview',
- 'views/searchviews',
- 'logger'
- ], function(
- config,
- searchProviders,
- util,
- Backbone,
- template,
- RouteController,
- searchCollections,
- StackHeadingListView,
- PostListView,
- Bridge,
- WidgetView,
- searchViews,
- logger
- ) {
- // We need to pass an empty array into empty collections that need options
- // (null gets interpreted as a value and wrapped). It's a bit fussy, but it
- // should be faster to share a single object than create one.
- var emptyArray = [];
- var bridge = new Bridge();
- var onStackVisit = function(view, model, e) {
- e.preventDefault();
- // Make a PUT request to the server to get a new session_id.
- // The model will come back as the first argument of success callback,
- // with a session_id added.
- model.save(null, {
- // Change the URL hit with this call to a PUT against
- // `lattice/:username/stack/search`. Instead of hard-coding, crawl
- // back up to the stack and grab its URL, just in case.
- // SiteModel.PlaceCollection.StackModel.StackCollection.
- url: model.parent().stack().parent().url(),
- success: function (model) {
- var data = util.extend(model.toJSON(), { origin: config.appName });
- bridge.changeStack(data).showViewer();
- },
- error: function(err){
- logger.error("Error encountered when handling stack launch", err);
- }
- });
- };
- var SearchController = RouteController.extend({
- initialize: function (options) {
- // Initialize collections object for storing search collections.
- var collections = this.collections = {};
- // Create an empty `StackSearchCollection` to be shared for
- // the lifetime of this controller. Views are relied on to
- // adapt to the changing contents of this controller.
- collections.stacks = new searchCollections.Stack(emptyArray, {
- urlTokens: { method: 'search' },
- urlQuery: {
- // Set the `limit` param to 4 -- the maximum number of stacks
- // that can be returned with the search.
- l: 4,
- // `q: query.terms,` should be manually configured in the `enter`
- // method using `terms` method.
- //
- // We want to use the `v3` version of this API -- it has the
- // `matches` property for searches.
- v: 3,
- // Set the maximum number of places to be returned by this API.
- p: 3
- }
- });
- // > Depending on how many providers we end up with, we may want to
- // > figure out a way to lazily create collections based on what we
- // > actually need. Prototypal objects are fast, though, so it might
- // > not actually matter -GB.
- collections.bings = new searchCollections.Bing(emptyArray, {
- urlTokens: {
- base: config.searchRoot,
- path: 'bing'
- },
- urlQuery: {
- // `q: query.terms,` should be manually configured in the `enter`
- // method using `terms` method.
- format: 'json'
- }
- });
- collections.tweets = new searchCollections.Tweets(emptyArray, {
- urlTokens: {
- base: config.searchRoot,
- path: 'twitter'
- },
- urlQuery: {
- // `q: query.terms,` should be manually configured in the `enter`
- // method using `terms` method.
- format: 'json'
- }
- });
- this.mainView = new searchViews.SearchTabsView();
- var stackListView = this.stacks(collections.stacks);
- var bingListView = this.bings(collections.bings);
- var tweetListView = this.tweets(collections.tweets);
- this.mainView.widgets({
- '#stack-results': stackListView,
- '#bing-results': bingListView,
- '#twitter-results': tweetListView
- });
- this.mainView.bind('activate', function (view) {
- var activeView = view.widget(view.active);
- var collection = activeView.collection;
- if (collection.state() === 'invalid') {
- activeView.render();
- collection.fetchDebounced();
- }
- });
- this.mainView.activateTab('#bing-results');
- },
- enter: function (query, options) {
- util.extend(this, options || {});
- // Set this view as active in the app.
- this.appView.widget(
- '#main',
- this.mainView
- );
- },
- update: function (query, options) {
- // Update instance from options.
- //
- // TODO: I'm not wild about the way this works -- it's too brittle -GB
- util.extend(this, options || {});
- var terms = this.parseQuery(query).terms;
- var collections = this.collections;
- var stacks = collections.stacks;
- // Update terms on all collections.
- for(var key in collections) collections[key].terms(terms);
- // Fetch stacks.
- stacks.fetchDebounced();
- // Fetch active search collection.
- this.mainView.widget(this.mainView.active).collection.fetchDebounced();
- },
- exit: function () {
- // Empty collections on exit.
- var collections = this.collections;
- for (var key in collections) collections[key].reset();
- // These memoized collections are available as properties on the
- // bings collection.
- this.collections.bings.images().reset();
- this.collections.bings.suggestions().reset();
- },
- // Centralizing the scratch logic it takes to create and configure a
- // stackListView for the search controller.
- stacks: function (collection) {
- var stackListView = new StackHeadingListView({
- className: 'headinglist headinglist--with-divider',
- collection: collection
- });
- stackListView.bind('visit', onStackVisit);
- return stackListView;
- },
- // Factory function for creating tweet result view with configured
- // click handlers. This is all a little bit obtuse, and could probably
- // be cleaner, but I want to avoid binding handlers in the view constructor.
- tweets: function (collection) {
- var searchTweetsView = new searchViews.SearchTweetsView({
- collection: collection
- });
- searchTweetsView.bind('visit', function (placeView, siteModel, e) {
- e.preventDefault();
- var terms = siteModel.collection.terms();
- var msg = util.extend(siteModel.toJSON(), {
- search_terms: terms,
- // TODO: GB: hard-coded. This should be pulled from a list of registered
- // search providers -- probably from an API somewhere. The client should
- // be dumb.
- search_provider: 'twitter',
- // TODO: GB: hard-coded. This should be pulled from a list of registered
- // search providers -- probably from an API somewhere. The client should
- // be dumb.
- search_url: template(config.searchResults)({
- query: terms,
- provider: 'twitter'
- }),
- origin: config.appName
- });
- // Clear search field. Reset to home.
- var appModel = this.appView.model;
- setTimeout(util.bind(appModel.set, appModel, { search: '' }), 1000);
- // Send message up.
- bridge.createStack(msg);
- }, this);
- return searchTweetsView;
- },
- // Factory function for creating bing result view with configured
- // click handlers. This is all a little bit obtuse, and could probably
- // be cleaner, but I want to avoid binding handlers in the view constructor.
- bings: function (collection) {
- var bingView = new searchViews.SearchBingView({
- collection: collection
- });
- // Define a handler for `visit` events on the `view`.
- bingView.bind('visit', function (placeView, siteModel, e) {
- e.preventDefault();
- // Capture search terms by calling the custom `terms` method on the
- // this collection.
- // Capture terms from collection via closure, at event-time.
- var terms = collection.terms();
- var msg = util.extend(siteModel.toJSON(), {
- search_terms: terms,
- // TODO: GB: hard-coded. This should be pulled from a list of registered
- // search providers -- probably from an API somewhere. The client should
- // be dumb.
- search_provider: 'bing',
-
- search_url: template(config.searchResults)({
- query: terms,
- provider: 'bing'
- }),
- origin: config.appName
- });
- // We have to access this property at runtime, rather than via closure
- // because `bings` is called at initialization, but `this.appView` isn't
- // part of the object until it is mixed in via `enter` method.
- //
- // TODO: this is confusing and brittle. We should handle requirements
- // during construction.
- var appModel = this.appView.model;
- // Clear search field. Reset to home.
- setTimeout(util.bind(appModel.set, appModel, { search: '' }), 1000);
- // Send message up.
- bridge.createStack(msg);
- }, this)
- .bind('suggest', function (view, model, e) {
- e.preventDefault();
- // We have to access this property at runtime, rather than via closure
- // because `bings` is called at initialization, but `this.appView` isn't
- // part of the object until it is mixed in via `enter` method.
- //
- // TODO: this is confusing and brittle. We should handle requirements
- // during construction.
- var appModel = this.appView.model;
- appModel.set({ search: model.get('place_title') });
- }, this);
- return bingView;
- },
- parseQuery: function (query) {
- var // parse out the query - it can be something like twitter/term1/term2
- // or just 'term'
- terms = query.split('/'),
- // a master list of search providers we support, with name: prefix.
- // define in config maybe?
- providers = searchProviders;
- var parsed = {};
- // We consider the first term to be a provider if it matches one of the
- // providers in our list.
- if(terms.length > 1 && providers[terms[0]]) {
- parsed.provider = terms.shift();
- }
- parsed.terms = terms.join(' ');
- return parsed;
- }
- });
- return new SearchController();
- });