/src/main-bb.js
JavaScript | 328 lines | 130 code | 28 blank | 170 comment | 5 complexity | a93aefc51dc7d50d3bd47a49fca7ee95 MD5 | raw file
- define([
- 'config!',
-
- '$', 'underscore', 'backbone',
-
- 'lib/browsing',
- 'models/sitecollection', 'views/sitelistview',
- 'models/websearchmodel', 'views/websearchview',
- 'lib/bridge',
- 'views/widgetview',
- 'models/socialmodel', 'views/socialview',
- 'views/searchinputview',
- 'lib/decorateboot',
- 'xmessage'
- ],
- function (
- config,
- $, util, Backbone,
- browsing,
- SiteCollection, SiteListView,
- WebSearchModel, WebSearchView,
- bridge,
- WidgetView,
- SocialModel, SocialView,
- SearchInputView,
- decorateBoot,
- xmessage
- ) {
- var global = window || {};
-
- // App
- // -----------------------------------------------------------------------
-
- // Define an overall AppView that handles:
- //
- // * Updating the header/footer
- // * Binding events to elements that don't change across views.
- var superproto = WidgetView.prototype;
- var AppView = WidgetView.extend({
- el: $('#main-page'),
-
- // Use $.delegate to bind event handlers to elements inside
- // this element "live" -- in other words, if an element is brought
- // in with AJAX, the handler will attach as soon as the element exists.
- events: {
- 'click #header a': 'onlinkclick',
- 'click .menu-item': 'togglemenu',
- 'click #config-menu .menu-item': 'onconfigbuttonclick'
- },
-
- initialize: function (options) {
- options = options || {};
- superproto.initialize.call(this, options);
- },
-
- // Define handler to intercept link clicks and treat as router navigation
- onlinkclick: function(e) {
- e.preventDefault();
- e.stopPropagation();
- // use the link href to get the route path
- // pathname has a '/' prefix, so strip that off
- var path = e.target.pathname;
- bridge.navigate(path, true);
- },
-
- togglemenu: function (e) {
- var $toggle = $(e.currentTarget),
- $menu = $toggle.next().filter('.submenu'); // zepto's next() doesn't accept a selector
-
- // console.log("togglemenu with: ", e.currentTarget, $menu);
- if ($menu.length < 1) {
- // no submenu to toggle
- $toggle.closest(".submenu").hide();
- return;
- }
-
- if ($menu.css('display') !== 'none') {
- $menu.hide();
- }
- else {
- $menu.show();
- }
-
- e.preventDefault();
- e.stopPropagation();
- },
-
- onconfigbuttonclick: function(e){
- var btn = e.currentTarget,
- action = btn.getAttribute("data-action");
-
- switch(action){
- case "wipe":
- global.location = config.applicationRoot + "account/wipe";
- break;
- case "logout":
- $.ajax({
- url: config.apiRoot + "logout",
- type: "POST",
- async: false,
- dataType: "json",
- success: function(data, status, xhr) {
- // console.log("logout success");
- location.href = config.applicationRoot;
- },
- error: function(aXhr, aStatus, aErrorThrown) {
- console.warn("logout error: ", aErrorThrown);
- }
- });
- break;
- default:
- console.log("unknown config action: " + action);
- }
- // stop click handling here. return earlier to allow the event to bubble
- e.preventDefault();
- e.stopPropagation();
- },
-
- render: function () {
- // Add username to username area.
- this.$('.username').text(this.model.get('username'));
- return this;
- }
- });
-
- // Backbone's router and controllers are both bundled into this single
- // construct. `routes` registers routes, ties them to events and calls the
- // corresponding controller method. The corresponding controller is called
- // using the router's `this` context. This makes the router a good thing to
- // use as an app reference object.
- //
- // Given this situation, controllers are pretty thin, and mostly just handle
- // gluing together AppView and any associated models.
- var AppRouter = Backbone.Router.extend({
- initialize: function () {
- console.log("AppRouter initialize, with config: ", config);
-
- // The auth! plugin populates config with user data
- // Create a model for convenient/consistent acess to that
- var modelWithUserData = config.user = new Backbone.Model({
- csrf_token: config.csrf_token,
- username: config.username
- });
-
- var appView = new AppView({
- // App and state data should be stored on this model.
- model: modelWithUserData
- });
- appView.render();
-
- this.appView = appView;
-
- // Set up the search input control
- console.log("creating search view");
-
- // SearchInputView is attached to real DOM elements -- no need to
- // set as a widget.
- var searchView = new SearchInputView({
- el: $('#search-bar-container'),
- // Re-use main model rather than trying to keep separate models in
- // sync using events. If complexity grows, it might be worth splitting
- // these into separate models and syncing with events.
- model: modelWithUserData
- });
- }
-
- });
-
- // Initialize app router.
- var app = global.app = new AppRouter();
-
- // Configure routes. Route controllers are called every time the route is
- // matched. To handle items that should only be run once per controller, we
- // use a handy decorator that will run the first function given only once.
-
- // Main (Home) Controller.
- app.route('main', 'main', decorateBoot(
- // Run a boot-up once.
- function () {
- console.log('Booting Main Controller');
-
- // Set up site list with a new collection (default is top sites).
- var sites = new SiteListView({
- collection: new SiteCollection()
- });
-
- return {
- sites: sites
- };
- },
- function (booted) {
- var sites = booted.sites;
- this.appView.widgets({ '#content': sites });
- sites.collection.fetch({ diff: true });
- }
- ));
-
- // Search Controller.
- app.route('search/*query', 'search', decorateBoot(
- // Run a boot-up once.
- function (query) {
- console.log('Booting Search Controller with query:', query);
- // Create a new view for the main content area.
- var mainView = new WidgetView({
- html: '<div class="sites"></div><div class="search"></div>'
- });
- // Register sites widget.
- var sites = new SiteListView({
- collection: new SiteCollection({}, {
- tokens: { path: 'search' },
- query: { limit: 12 }
- })
- });
- // set up the search control
- var webSearchResults = new WebSearchView({
- model: new WebSearchModel()
- });
- var appViewModel = this.appView.model;
- // Bind to the webSearchResults widget's search:change` event.
- // When the search is changed via the view, update the search records.
- webSearchResults.bind('search:change', function(query){
- console.log("search:change event from the search widget: ", query);
- // keep the app bridge in sync
- if('search' in query){
- // Update the webSearchView's model data with query info.
- this.model.set(query, { silent: true });
- // only if the query changes do we need to update the
- // history/location
- appViewModel.set(query);
- }
- });
- // Register for the visit* events which are fired from the bridge (via the browsing module)
- // when a site visit occurs
- browsing.bind('registervisit', function(visitData){
- console.log("registervisit from the browsing module: ", visitData);
- });
- // Set widgets on mainView.
- mainView.widgets({
- '.sites': sites,
- '.search': webSearchResults
- });
- return {
- mainView: mainView
- };
- },
- function (booted, query) {
- // handling search route.
- global.currentController = booted;
- var mainView = booted.mainView,
- sites = mainView.widget('.sites'),
- webSearchResults = mainView.widget('.search'),
- params = {};
- this.appView.widgets({ '#content': booted.mainView });
- // parse out the query - it can be something like twitter/term1/term2 or just 'term'
- var terms = query.split('/'),
- // a master list of search providers we support, with name: prefix.
- // define in config maybe?
- providers = config.searchProviders;
- if(2 >= terms.length && terms[0] in providers) {
- // change search provider
- webSearchResults.model.set({
- 'provider': terms.shift() // pull the provider out of the search terms
- });
- }
- // api search
- var url = sites.collection.url(null, { q: terms.join(' ') });
- console.log("search controller, query: ", url);
- sites.fetch({ diff: true });
- // sync the search widget
- var searchUrl = webSearchResults.model.set({ query: terms.join(' ') });
- // web (bing/twitter) search
- webSearchResults.model.fetch();
- }
- ));
- // social controller
- app.route('social', 'social', decorateBoot(
- function () {
- console.log('Booting Social Controller');
- var socialModel = new SocialModel(),
- mainView = new SocialView({
- model: socialModel
- });
-
- return {
- socialModel: socialModel,
- mainView: mainView
- };
- },
- function (booted) {
- this.appView.widgets({ '#content': booted.mainView });
- // Hit the JSON API, create the various models and collections for this
- // view and store them in the SocialModel instance.
- booted.socialModel.fetch();
- }
- ));
-
- // Set a "static" reference to the router instance on bridge.
- // Used by bridge.navigate (a facade for Router.prototype.navigate).
- bridge.setActiveRouter(app);
-
- // Now that all of our routes are configured, start router listener.
- Backbone.history.start({pushState: true});
-
- // Export a limited API to the global space. To be consumed by native app.
- global.PANCAKE = bridge;
-
- // notify the 'top' window we're loaded
- xmessage.sendMessage("top", "ready", [{
- source: global.name, type: "ready"
- }], function (result) {});
- // Return app object for reference
- return app;
- });