From 5848af77650dbf1d096ce8f6dd7d155ff4ed7ec4 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 10 Sep 2015 17:43:48 +0800 Subject: [PATCH 001/122] update Gemfile for rails 4 --- Gemfile | 26 +---- Gemfile.lock | 266 +++++++++++++++++++++++++++------------------------ 2 files changed, 147 insertions(+), 145 deletions(-) diff --git a/Gemfile b/Gemfile index e21cca7e..1ac16137 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,7 @@ source 'https://rubygems.org' ruby '2.1.3' -gem 'rails', '3.2.17' - -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' +gem 'rails', '4.2.4' gem 'devise' gem 'redis' @@ -28,32 +25,19 @@ gem 'aws-sdk' # in production environments by default. group :assets do gem 'sass-rails' - gem 'coffee-rails', '~> 3.2.1' + gem 'coffee-rails' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer' - gem 'uglifier', '>= 1.0.3' + gem 'uglifier' end group :production do #this is used on heroku #gem 'rmagick' end -gem 'jquery-rails', '2.1.2' - -# To use ActiveModel has_secure_password -# gem 'bcrypt-ruby', '~> 3.0.0' +gem 'jquery-rails' # To use Jbuilder templates for JSON - gem 'jbuilder', '0.8.2' - -# Use unicorn as the web server -# gem 'unicorn' - -# Deploy with Capistrano -# gem 'capistrano' - -# To use debugger -# gem 'ruby-debug19', :require => 'ruby-debug' - +gem 'jbuilder' diff --git a/Gemfile.lock b/Gemfile.lock index e46dfc6b..b027a680 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,58 +1,66 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.17) - actionpack (= 3.2.17) - mail (~> 2.5.4) - actionpack (3.2.17) - activemodel (= 3.2.17) - activesupport (= 3.2.17) - builder (~> 3.0.0) + actionmailer (4.2.4) + actionpack (= 4.2.4) + actionview (= 4.2.4) + activejob (= 4.2.4) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.4) + actionview (= 4.2.4) + activesupport (= 4.2.4) + rack (~> 1.6) + rack-test (~> 0.6.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.4) + activesupport (= 4.2.4) + builder (~> 3.1) erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.5) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.2.1) - activemodel (3.2.17) - activesupport (= 3.2.17) - builder (~> 3.0.0) - activerecord (3.2.17) - activemodel (= 3.2.17) - activesupport (= 3.2.17) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.17) - activemodel (= 3.2.17) - activesupport (= 3.2.17) - activesupport (3.2.17) - i18n (~> 0.6, >= 0.6.4) - multi_json (~> 1.0) - arel (3.0.3) - aws-sdk (1.54.0) - aws-sdk-v1 (= 1.54.0) - aws-sdk-v1 (1.54.0) - json (~> 1.4) - nokogiri (>= 1.4.4) - bcrypt (3.1.7) - bcrypt (3.1.7-x86-mingw32) - best_in_place (2.1.0) - jquery-rails - rails (~> 3.1) - builder (3.0.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + activejob (4.2.4) + activesupport (= 4.2.4) + globalid (>= 0.3.0) + activemodel (4.2.4) + activesupport (= 4.2.4) + builder (~> 3.1) + activerecord (4.2.4) + activemodel (= 4.2.4) + activesupport (= 4.2.4) + arel (~> 6.0) + activesupport (4.2.4) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + arel (6.0.3) + aws-sdk (2.1.19) + aws-sdk-resources (= 2.1.19) + aws-sdk-core (2.1.19) + jmespath (~> 1.0) + aws-sdk-resources (2.1.19) + aws-sdk-core (= 2.1.19) + bcrypt (3.1.10) + best_in_place (3.0.3) + actionpack (>= 3.2) + railties (>= 3.2) + builder (3.2.2) cancan (1.6.10) climate_control (0.0.3) activesupport (>= 3.0) - cocaine (0.5.4) + cocaine (0.5.7) climate_control (>= 0.0.3, < 1.0) - coffee-rails (3.2.2) + coffee-rails (4.1.0) coffee-script (>= 2.2.0) - railties (~> 3.2.0) - coffee-script (2.3.0) + railties (>= 4.0.0, < 5.0) + coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.8.0) - devise (3.4.0) + coffee-script-source (1.9.1.1) + devise (3.5.2) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) @@ -60,92 +68,100 @@ GEM thread_safe (~> 0.1) warden (~> 1.2.3) erubis (2.7.0) - execjs (2.2.1) + execjs (2.6.0) ezcrypto (0.7.2) - formtastic (3.0.0) + formtastic (3.1.3) actionpack (>= 3.2.13) - formula (1.0.1) + formula (1.1.1) rails (> 3.0.0) - hike (1.2.3) - i18n (0.6.11) - jbuilder (0.8.2) - activesupport (>= 3.0.0) - journey (1.0.4) - jquery-rails (2.1.2) - railties (>= 3.1.0, < 5.0) - thor (~> 0.14) - json (1.8.1) - kaminari (0.16.1) + globalid (0.3.6) + activesupport (>= 4.1.0) + i18n (0.7.0) + jbuilder (2.3.1) + activesupport (>= 3.0.0, < 5) + multi_json (~> 1.2) + jmespath (1.0.2) + multi_json (~> 1.0) + jquery-rails (4.0.5) + rails-dom-testing (~> 1.0) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + json (1.8.3) + kaminari (0.16.3) actionpack (>= 3.0.0) activesupport (>= 3.0.0) - mail (2.5.4) - mime-types (~> 1.16) - treetop (~> 1.4.8) - mime-types (1.25.1) - mini_portile (0.6.0) - multi_json (1.10.1) - nokogiri (1.6.3.1) - mini_portile (= 0.6.0) - nokogiri (1.6.3.1-x86-mingw32) - mini_portile (= 0.6.0) + loofah (2.0.3) + nokogiri (>= 1.5.9) + mail (2.6.3) + mime-types (>= 1.16, < 3) + mime-types (2.6.1) + mimemagic (0.3.0) + mini_portile (0.6.2) + minitest (5.8.0) + multi_json (1.11.2) + nokogiri (1.6.6.2) + mini_portile (~> 0.6.0) oauth (0.4.7) orm_adapter (0.5.0) - paperclip (4.2.0) - activemodel (>= 3.0.0) - activesupport (>= 3.0.0) - cocaine (~> 0.5.3) + paperclip (4.3.0) + activemodel (>= 3.2.0) + activesupport (>= 3.2.0) + cocaine (~> 0.5.5) mime-types - pg (0.17.1) - pg (0.17.1-x86-mingw32) - polyglot (0.3.5) - rack (1.4.5) - rack-cache (1.2) - rack (>= 0.4) - rack-ssl (1.3.4) - rack - rack-test (0.6.2) + mimemagic (= 0.3.0) + pg (0.18.3) + rack (1.6.4) + rack-test (0.6.3) rack (>= 1.0) - rails (3.2.17) - actionmailer (= 3.2.17) - actionpack (= 3.2.17) - activerecord (= 3.2.17) - activeresource (= 3.2.17) - activesupport (= 3.2.17) - bundler (~> 1.0) - railties (= 3.2.17) - rails3-jquery-autocomplete (1.0.14) - rails (>= 3.0) - railties (3.2.17) - actionpack (= 3.2.17) - activesupport (= 3.2.17) - rack-ssl (~> 1.3.2) + rails (4.2.4) + actionmailer (= 4.2.4) + actionpack (= 4.2.4) + actionview (= 4.2.4) + activejob (= 4.2.4) + activemodel (= 4.2.4) + activerecord (= 4.2.4) + activesupport (= 4.2.4) + bundler (>= 1.3.0, < 2.0) + railties (= 4.2.4) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.7) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.2) + loofah (~> 2.0) + rails3-jquery-autocomplete (1.0.15) + rails (>= 3.2) + railties (4.2.4) + actionpack (= 4.2.4) + activesupport (= 4.2.4) rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - rake (10.3.2) - rdoc (3.12.2) - json (~> 1.4) - redis (3.1.0) - responders (1.1.1) - railties (>= 3.2, < 4.2) - sass (3.4.5) - sass-rails (3.2.6) - railties (~> 3.2.0) - sass (>= 3.1.10) - tilt (~> 1.3) - sprockets (2.2.2) - hike (~> 1.2) - multi_json (~> 1.0) + thor (>= 0.18.1, < 2.0) + rake (10.4.2) + redis (3.2.1) + responders (2.1.0) + railties (>= 4.2.0, < 5) + sass (3.4.18) + sass-rails (5.0.4) + railties (>= 4.0.0, < 5.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + sprockets (3.3.4) rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) + sprockets-rails (2.3.3) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (>= 2.8, < 4.0) thor (0.19.1) - thread_safe (0.3.4) - tilt (1.4.1) - treetop (1.4.15) - polyglot - polyglot (>= 0.3.1) - tzinfo (0.3.41) - uglifier (2.5.3) + thread_safe (0.3.5) + tilt (2.0.1) + tzinfo (1.2.2) + thread_safe (~> 0.1) + uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) uservoice-ruby (0.0.11) @@ -157,25 +173,27 @@ GEM PLATFORMS ruby - x86-mingw32 DEPENDENCIES aws-sdk best_in_place cancan - coffee-rails (~> 3.2.1) + coffee-rails devise formtastic formula - jbuilder (= 0.8.2) - jquery-rails (= 2.1.2) + jbuilder + jquery-rails json kaminari paperclip pg - rails (= 3.2.17) + rails (= 4.2.4) rails3-jquery-autocomplete redis sass-rails - uglifier (>= 1.0.3) + uglifier uservoice-ruby + +BUNDLED WITH + 1.10.6 From 3c0bdff823f71b2843b5300957e05aac310ee7e6 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 10 Sep 2015 17:43:58 +0800 Subject: [PATCH 002/122] some config changes for rails 4 found on the Internet --- config/application.rb | 7 ------- config/environments/development.rb | 15 ++------------- config/environments/production.rb | 3 +++ config/environments/test.rb | 8 ++------ 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/config/application.rb b/config/application.rb index d9f5f0f1..37547b27 100644 --- a/config/application.rb +++ b/config/application.rb @@ -44,14 +44,7 @@ module Metamaps # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql - # Enforce whitelist mode for mass assignment. - # This will create an empty whitelist of attributes available for mass-assignment for all models - # in your app. As such, your models will need to explicitly whitelist or blacklist accessible - # parameters by using an attr_accessible or attr_protected declaration. - # config.active_record.whitelist_attributes = true - # Enable the asset pipeline - config.assets.enabled = true config.assets.initialize_on_precompile = false # Version of your assets, change this if you want to expire all your assets diff --git a/config/environments/development.rb b/config/environments/development.rb index 74431de7..251e12ab 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,14 +1,13 @@ Metamaps::Application.configure do # Settings specified here will take precedence over those in config/application.rb + config.eager_load = false + # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # Log error messages when you accidentally call methods on nil. - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false @@ -40,16 +39,6 @@ Metamaps::Application.configure do # Print deprecation notices to the Rails logger config.active_support.deprecation = :log - # Only use best-standards-support built into browsers - config.action_dispatch.best_standards_support = :builtin - - # Raise exception on mass assignment protection for Active Record models - config.active_record.mass_assignment_sanitizer = :strict - - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL) - config.active_record.auto_explain_threshold_in_seconds = 0.5 - # Do not compress assets config.assets.compress = false diff --git a/config/environments/production.rb b/config/environments/production.rb index 2186327f..17773071 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,6 +1,9 @@ Metamaps::Application.configure do # Settings specified here will take precedence over those in config/application.rb + config.eager_load = true + config.assets.js_compressor = :uglifier + # Code is not reloaded between requests config.cache_classes = true diff --git a/config/environments/test.rb b/config/environments/test.rb index 8a7b408c..18c17296 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,6 +1,8 @@ Metamaps::Application.configure do # Settings specified here will take precedence over those in config/application.rb + config.eager_load = false + # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped @@ -11,9 +13,6 @@ Metamaps::Application.configure do config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" - # Log error messages when you accidentally call methods on nil - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false @@ -29,9 +28,6 @@ Metamaps::Application.configure do # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - # Raise exception on mass assignment protection for Active Record models - config.active_record.mass_assignment_sanitizer = :strict - # Print deprecation notices to the stderr config.active_support.deprecation = :stderr end From c250f5d39567e8a91c033c89e06b8d1bf12d45ca Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 10 Sep 2015 22:03:39 +0800 Subject: [PATCH 003/122] asset precompiling simpler in rails 4 --- config/application.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/config/application.rb b/config/application.rb index 37547b27..0a5e6b51 100644 --- a/config/application.rb +++ b/config/application.rb @@ -2,12 +2,7 @@ require File.expand_path('../boot', __FILE__) require 'rails/all' -if defined?(Bundler) - # If you precompile assets before deploying to production, use this line - Bundler.require(*Rails.groups(:assets => %w(development test))) - # If you want your assets lazily compiled in production, use this line - # Bundler.require(:default, :assets, Rails.env) -end +Bundler.require(:default, Rails.env) module Metamaps class Application < Rails::Application From 7a73f3bc0772f026f9b7397b0fc90ca02bab0491 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 10 Sep 2015 22:06:58 +0800 Subject: [PATCH 004/122] remove match method from routes.rb --- config/routes.rb | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 494992a6..3c79f881 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,12 +2,12 @@ Metamaps::Application.routes.draw do root to: 'main#home', via: :get - match 'request', to: 'main#requestinvite', via: :get, as: :request + get 'request', to: 'main#requestinvite', as: :request - match 'search/topics', to: 'main#searchtopics', via: :get, as: :searchtopics - match 'search/maps', to: 'main#searchmaps', via: :get, as: :searchmaps - match 'search/mappers', to: 'main#searchmappers', via: :get, as: :searchmappers - match 'search/synapses', to: 'main#searchsynapses', via: :get, as: :searchsynapses + get 'search/topics', to: 'main#searchtopics', as: :searchtopics + get 'search/maps', to: 'main#searchmaps', as: :searchmaps + get 'search/mappers', to: 'main#searchmappers', as: :searchmappers + get 'search/synapses', to: 'main#searchsynapses', as: :searchsynapses resources :mappings, except: [:index, :new, :edit] resources :metacode_sets, :except => [:show] @@ -16,28 +16,28 @@ Metamaps::Application.routes.draw do resources :topics, except: [:index, :new, :edit] do get :autocomplete_topic, :on => :collection end - match 'topics/:id/network', to: 'topics#network', via: :get, as: :network - match 'topics/:id/relative_numbers', to: 'topics#relative_numbers', via: :get, as: :relative_numbers - match 'topics/:id/relatives', to: 'topics#relatives', via: :get, as: :relatives + get 'topics/:id/network', to: 'topics#network', as: :network + get 'topics/:id/relative_numbers', to: 'topics#relative_numbers', as: :relative_numbers + get 'topics/:id/relatives', to: 'topics#relatives', as: :relatives - match 'explore/active', to: 'maps#index', via: :get, as: :activemaps - match 'explore/featured', to: 'maps#index', via: :get, as: :featuredmaps - match 'explore/mine', to: 'maps#index', via: :get, as: :mymaps - match 'explore/mapper/:id', to: 'maps#index', via: :get, as: :usermaps + get 'explore/active', to: 'maps#index', as: :activemaps + get 'explore/featured', to: 'maps#index', as: :featuredmaps + get 'explore/mine', to: 'maps#index', as: :mymaps + get 'explore/mapper/:id', to: 'maps#index', as: :usermaps resources :maps, except: [:new, :edit] - match 'maps/:id/contains', to: 'maps#contains', via: :get, as: :contains - match 'maps/:id/upload_screenshot', to: 'maps#screenshot', via: :post, as: :screenshot + get 'maps/:id/contains', to: 'maps#contains', as: :contains + get 'maps/:id/upload_screenshot', to: 'maps#screenshot', as: :screenshot - devise_for :users, controllers: { registrations: 'users/registrations', passwords: 'users/passwords', sessions: 'devise/sessions' }, :skip => [:sessions] + devise_for :users, controllers: { registrations: 'users/registrations', passwords: 'users/passwords', sessions: 'devise/sessions' }, :skip => :sessions devise_scope :user do get 'login' => 'devise/sessions#new', :as => :new_user_session post 'login' => 'devise/sessions#create', :as => :user_session get 'logout' => 'devise/sessions#destroy', :as => :destroy_user_session - get 'join' => 'devise/registrations#new', :as => :new_user_registration +# get 'join' => 'devise/registrations#new', :as => :new_user_registration end - match 'users/:id/details', to: 'users#details', via: :get, as: :details - match 'user/updatemetacodes', to: 'users#updatemetacodes', via: :post, as: :updatemetacodes + get 'users/:id/details', to: 'users#details', as: :details + post 'user/updatemetacodes', to: 'users#updatemetacodes', as: :updatemetacodes resources :users, except: [:index, :destroy] end From f81fdbf8cc144c05a2ab3ea1f710de7cc97b190f Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 10 Sep 2015 22:12:39 +0800 Subject: [PATCH 005/122] secret_token => secret_key_base --- config/initializers/secret_token.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index b9f46222..9c1fb05e 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -4,4 +4,4 @@ # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. -Metamaps::Application.config.secret_token = '267c8a84f63963282f45bc3010eaddf027abfab58fc759d6e239c8005f85ee99d6d01b1ab6394cdee9ca7f8c9213a0cf91d3d8d3350f096123e2caccbcc0924f' +Metamaps::Application.config.secret_key_base = '267c8a84f63963282f45bc3010eaddf027abfab58fc759d6e239c8005f85ee99d6d01b1ab6394cdee9ca7f8c9213a0cf91d3d8d3350f096123e2caccbcc0924f' From dc51d0080ecc8d5f11c097a271d0034d37fd0f82 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 10 Sep 2015 22:12:50 +0800 Subject: [PATCH 006/122] remove attr_accessible --- app/controllers/metacode_sets_controller.rb | 4 ++++ app/controllers/users_controller.rb | 4 ++++ app/models/in_metacode_set.rb | 1 - app/models/metacode_set.rb | 1 - app/models/user.rb | 2 -- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/controllers/metacode_sets_controller.rb b/app/controllers/metacode_sets_controller.rb index 4542f6dd..6560492a 100644 --- a/app/controllers/metacode_sets_controller.rb +++ b/app/controllers/metacode_sets_controller.rb @@ -1,6 +1,10 @@ class MetacodeSetsController < ApplicationController before_filter :require_admin + + def metacode_set_params + params.require(:metacode_set).permit(:desc, :mapperContributed, :name) + end # GET /metacode_sets # GET /metacode_sets.json diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 58ef2d96..81ed2122 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,6 +3,10 @@ class UsersController < ApplicationController before_filter :require_user, only: [:edit, :update, :updatemetacodes] respond_to :html, :json + + def user_params + params.require(:user).permit(:name, :email, :image, :password, + :password_confirmation, :code, :joinedwithcode, :remember_me) # GET /users/1.json def show diff --git a/app/models/in_metacode_set.rb b/app/models/in_metacode_set.rb index 3f608587..117033d6 100644 --- a/app/models/in_metacode_set.rb +++ b/app/models/in_metacode_set.rb @@ -1,5 +1,4 @@ class InMetacodeSet < ActiveRecord::Base belongs_to :metacode, :class_name => "Metacode", :foreign_key => "metacode_id" belongs_to :metacode_set, :class_name => "MetacodeSet", :foreign_key => "metacode_set_id" - # attr_accessible :title, :body end diff --git a/app/models/metacode_set.rb b/app/models/metacode_set.rb index 6798e568..d06de1e4 100644 --- a/app/models/metacode_set.rb +++ b/app/models/metacode_set.rb @@ -1,6 +1,5 @@ class MetacodeSet < ActiveRecord::Base belongs_to :user - attr_accessible :desc, :mapperContributed, :name has_many :in_metacode_sets has_many :metacodes, :through => :in_metacode_sets end diff --git a/app/models/user.rb b/app/models/user.rb index 2e738134..44bd6b4a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -11,8 +11,6 @@ class User < ActiveRecord::Base devise :database_authenticatable, :recoverable, :rememberable, :trackable, :registerable - attr_accessible :name, :email, :image, :password, :password_confirmation, :code, :joinedwithcode, :remember_me - serialize :settings, UserPreference validates :password, :presence => true, From 8cf82775734b5882de911feea2566272565dd331 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 10 Sep 2015 22:13:10 +0800 Subject: [PATCH 007/122] add jquery_ui to gemfile --- Gemfile | 1 + Gemfile.lock | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 1ac16137..2ba0e0a9 100644 --- a/Gemfile +++ b/Gemfile @@ -38,6 +38,7 @@ group :production do #this is used on heroku end gem 'jquery-rails' +gem 'jquery-ui-rails' # To use Jbuilder templates for JSON gem 'jbuilder' diff --git a/Gemfile.lock b/Gemfile.lock index b027a680..792c5779 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -86,6 +86,8 @@ GEM rails-dom-testing (~> 1.0) railties (>= 4.2.0) thor (>= 0.14, < 2.0) + jquery-ui-rails (5.0.5) + railties (>= 3.2.16) json (1.8.3) kaminari (0.16.3) actionpack (>= 3.0.0) @@ -184,6 +186,7 @@ DEPENDENCIES formula jbuilder jquery-rails + jquery-ui-rails json kaminari paperclip From d67d098b2ac61f082c423673a09cc3a339eb215a Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 10 Sep 2015 22:40:45 +0800 Subject: [PATCH 008/122] remove old jquery-ui file --- .../lib/jquery-ui-1.8.23.custom.min.js | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 app/assets/javascripts/lib/jquery-ui-1.8.23.custom.min.js diff --git a/app/assets/javascripts/lib/jquery-ui-1.8.23.custom.min.js b/app/assets/javascripts/lib/jquery-ui-1.8.23.custom.min.js deleted file mode 100644 index 564759cd..00000000 --- a/app/assets/javascripts/lib/jquery-ui-1.8.23.custom.min.js +++ /dev/null @@ -1,25 +0,0 @@ -/*! jQuery UI - v1.8.23 - 2012-08-15 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.core.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.23",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a("").outerWidth(1).jquery||a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:a.expr.createPseudo?a.expr.createPseudo(function(b){return function(c){return!!a.data(c,b)}}):function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.curCSS||(a.curCSS=a.css),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a=9||!!b.button?this._mouseStarted?(this._mouseDrag(b),b.preventDefault()):(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,b)!==!1,this._mouseStarted?this._mouseDrag(b):this._mouseUp(b)),!this._mouseStarted):this._mouseUp(b)},_mouseUp:function(b){return a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,b.target==this._mouseDownEvent.target&&a.data(b.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(b)),!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(a){return this.mouseDelayMet},_mouseStart:function(a){},_mouseDrag:function(a){},_mouseStop:function(a){},_mouseCapture:function(a){return!0}})})(jQuery);;/*! jQuery UI - v1.8.23 - 2012-08-15 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.position.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;return i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1],this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),a.curCSS||(a.curCSS=a.css),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);;/*! jQuery UI - v1.8.23 - 2012-08-15 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.draggable.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},destroy:function(){if(!this.element.data("draggable"))return;return this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy(),this},_mouseCapture:function(b){var c=this.options;return this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(b),this.handle?(c.iframeFix&&a(c.iframeFix===!0?"iframe":c.iframeFix).each(function(){a('
').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(a(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(b){var c=this.options;return this.helper=this._createHelper(b),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),a.ui.ddmanager&&(a.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt),c.containment&&this._setContainment(),this._trigger("start",b)===!1?(this._clear(),!1):(this._cacheHelperProportions(),a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this._mouseDrag(b,!0),a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,b),!0)},_mouseDrag:function(b,c){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute");if(!c){var d=this._uiHash();if(this._trigger("drag",b,d)===!1)return this._mouseUp({}),!1;this.position=d.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),!1},_mouseStop:function(b){var c=!1;a.ui.ddmanager&&!this.options.dropBehaviour&&(c=a.ui.ddmanager.drop(this,b)),this.dropped&&(c=this.dropped,this.dropped=!1);var d=this.element[0],e=!1;while(d&&(d=d.parentNode))d==document&&(e=!0);if(!e&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===!0||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var f=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){f._trigger("stop",b)!==!1&&f._clear()})}else this._trigger("stop",b)!==!1&&this._clear();return!1},_mouseUp:function(b){return this.options.iframeFix===!0&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,b),a.ui.mouse.prototype._mouseUp.call(this,b)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?!0:!1;return a(this.options.handle,this.element).find("*").andSelf().each(function(){this==b.target&&(c=!0)}),c},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo),d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute"),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[b.containment=="document"?0:a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,b.containment=="document"?0:a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(b.containment=="document"?0:a(window).scrollLeft())+a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(b.containment=="document"?0:a(window).scrollTop())+(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)&&b.containment.constructor!=Array){var c=a(b.containment),d=c[0];if(!d)return;var e=c.offset(),f=a(d).css("overflow")!="hidden";this.containment=[(parseInt(a(d).css("borderLeftWidth"),10)||0)+(parseInt(a(d).css("paddingLeft"),10)||0),(parseInt(a(d).css("borderTopWidth"),10)||0)+(parseInt(a(d).css("paddingTop"),10)||0),(f?Math.max(d.scrollWidth,d.offsetWidth):d.offsetWidth)-(parseInt(a(d).css("borderLeftWidth"),10)||0)-(parseInt(a(d).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(f?Math.max(d.scrollHeight,d.offsetHeight):d.offsetHeight)-(parseInt(a(d).css("borderTopWidth"),10)||0)-(parseInt(a(d).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=c}else b.containment.constructor==Array&&(this.containment=b.containment)},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName),f=b.pageX,g=b.pageY;if(this.originalPosition){var h;if(this.containment){if(this.relative_container){var i=this.relative_container.offset();h=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]}else h=this.containment;b.pageX-this.offset.click.lefth[2]&&(f=h[2]+this.offset.click.left),b.pageY-this.offset.click.top>h[3]&&(g=h[3]+this.offset.click.top)}if(c.grid){var j=c.grid[1]?this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY;g=h?j-this.offset.click.toph[3]?j-this.offset.click.toph[2]?k-this.offset.click.left=0;k--){var l=d.snapElements[k].left,m=l+d.snapElements[k].width,n=d.snapElements[k].top,o=n+d.snapElements[k].height;if(!(l-f").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e=document.activeElement;try{e.id}catch(f){e=document.body}return b.wrap(d),(b[0]===e||a.contains(b[0],e))&&a(e).focus(),d=b.parent(),b.css("position")=="static"?(d.css({position:"relative"}),b.css({position:"relative"})):(a.extend(c,{position:b.css("position"),zIndex:b.css("z-index")}),a.each(["top","left","bottom","right"],function(a,d){c[d]=b.css(d),isNaN(parseInt(c[d],10))&&(c[d]="auto")}),b.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),d.css(c).show()},removeWrapper:function(b){var c,d=document.activeElement;return b.parent().is(".ui-effects-wrapper")?(c=b.parent().replaceWith(b),(b[0]===d||a.contains(b[0],d))&&a(d).focus(),c):b},setTransition:function(b,c,d,e){return e=e||{},a.each(c,function(a,c){var f=b.cssUnit(c);f[0]>0&&(e[c]=f[0]*d+f[1])}),e}}),a.fn.extend({effect:function(b,c,d,e){var f=k.apply(this,arguments),g={options:f[1],duration:f[2],callback:f[3]},h=g.options.mode,i=a.effects[b];return a.fx.off||!i?h?this[h](g.duration,g.callback):this.each(function(){g.callback&&g.callback.call(this)}):i.call(this,g)},_show:a.fn.show,show:function(a){if(l(a))return this._show.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="show",this.effect.apply(this,b)},_hide:a.fn.hide,hide:function(a){if(l(a))return this._hide.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="hide",this.effect.apply(this,b)},__toggle:a.fn.toggle,toggle:function(b){if(l(b)||typeof b=="boolean"||a.isFunction(b))return this.__toggle.apply(this,arguments);var c=k.apply(this,arguments);return c[1].mode="toggle",this.effect.apply(this,c)},cssUnit:function(b){var c=this.css(b),d=[];return a.each(["em","px","%","pt"],function(a,b){c.indexOf(b)>0&&(d=[parseFloat(c),b])}),d}});var m={};a.each(["Quad","Cubic","Quart","Quint","Expo"],function(a,b){m[b]=function(b){return Math.pow(b,a+2)}}),a.extend(m,{Sine:function(a){return 1-Math.cos(a*Math.PI/2)},Circ:function(a){return 1-Math.sqrt(1-a*a)},Elastic:function(a){return a===0||a===1?a:-Math.pow(2,8*(a-1))*Math.sin(((a-1)*80-7.5)*Math.PI/15)},Back:function(a){return a*a*(3*a-2)},Bounce:function(a){var b,c=4;while(a<((b=Math.pow(2,--c))-1)/11);return 1/Math.pow(4,3-c)-7.5625*Math.pow((b*3-2)/22-a,2)}}),a.each(m,function(b,c){a.easing["easeIn"+b]=c,a.easing["easeOut"+b]=function(a){return 1-c(1-a)},a.easing["easeInOut"+b]=function(a){return a<.5?c(a*2)/2:c(a*-2+2)/-2+1}})}(jQuery);; \ No newline at end of file From a8cef723a24b1c5e216dc8891a3ff7319a458217 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 10 Sep 2015 22:48:35 +0800 Subject: [PATCH 009/122] fix has_many relationships in map & mapping models for rails 4 --- app/models/map.rb | 7 ++----- app/models/mapping.rb | 3 +++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/map.rb b/app/models/map.rb index 3bf5a4a6..dffb9fae 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -2,11 +2,8 @@ class Map < ActiveRecord::Base belongs_to :user - has_many :topicmappings, :class_name => 'Mapping', :conditions => {:category => 'Topic'} - has_many :synapsemappings, :class_name => 'Mapping', :conditions => {:category => 'Synapse'} - - has_many :topics, :through => :topicmappings - has_many :synapses, :through => :synapsemappings + has_many :topics, -> { Mapping.topicmapping }, :through => :topicmappings + has_many :synapses, -> { Mapping.synapsemapping }, :through => :synapsemappings # This method associates the attribute ":image" with a file attachment has_attached_file :screenshot, :styles => { diff --git a/app/models/mapping.rb b/app/models/mapping.rb index dc50730c..9d6b4947 100644 --- a/app/models/mapping.rb +++ b/app/models/mapping.rb @@ -1,5 +1,8 @@ class Mapping < ActiveRecord::Base + scope :topicmapping, -> { where (category: :Topic) } + scope :synapsemapping, -> { where (category: :Synapse) } + belongs_to :topic, :class_name => "Topic", :foreign_key => "topic_id" belongs_to :synapse, :class_name => "Synapse", :foreign_key => "synapse_id" belongs_to :map, :class_name => "Map", :foreign_key => "map_id" From c361349c20d283b6b2f22c2eed5c4debeb26269f Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Sat, 19 Sep 2015 16:26:34 +0800 Subject: [PATCH 010/122] add required params to all controllers --- app/controllers/mappings_controller.rb | 6 ++++++ app/controllers/maps_controller.rb | 7 +++++++ app/controllers/metacode_sets_controller.rb | 11 +++++++---- app/controllers/metacodes_controller.rb | 7 +++++++ app/controllers/registrations_controller.rb | 2 +- app/controllers/synapses_controller.rb | 6 ++++++ app/controllers/topics_controller.rb | 7 ++++++- app/controllers/users/passwords_controller.rb | 2 +- app/controllers/users_controller.rb | 10 ++++++---- 9 files changed, 47 insertions(+), 11 deletions(-) diff --git a/app/controllers/mappings_controller.rb b/app/controllers/mappings_controller.rb index b28c7638..4898ceb4 100644 --- a/app/controllers/mappings_controller.rb +++ b/app/controllers/mappings_controller.rb @@ -48,4 +48,10 @@ class MappingsController < ApplicationController head :no_content end + + private + # Never trust parameters from the scary internet, only allow the white list through. + def mapping_params + params.require(:mapping).permit(:id, :category, :xloc, :yloc, :topic_id, :synapse_id, :map_id, :user_id) + end end diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index 8f0ced9b..d37be5a3 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -238,4 +238,11 @@ class MapsController < ApplicationController } end end + + private + + # Never trust parameters from the scary internet, only allow the white list through. + def map_params + params.require(:map).permit(:id, :name, :arranged, :desc, :permission, :user_id) + end end diff --git a/app/controllers/metacode_sets_controller.rb b/app/controllers/metacode_sets_controller.rb index 6560492a..376babe5 100644 --- a/app/controllers/metacode_sets_controller.rb +++ b/app/controllers/metacode_sets_controller.rb @@ -2,10 +2,6 @@ class MetacodeSetsController < ApplicationController before_filter :require_admin - def metacode_set_params - params.require(:metacode_set).permit(:desc, :mapperContributed, :name) - end - # GET /metacode_sets # GET /metacode_sets.json def index @@ -120,4 +116,11 @@ class MetacodeSetsController < ApplicationController format.json { head :no_content } end end + + private + + def metacode_set_params + params.require(:metacode_set).permit(:desc, :mapperContributed, :name) + end + end diff --git a/app/controllers/metacodes_controller.rb b/app/controllers/metacodes_controller.rb index 810981dc..25a7c096 100644 --- a/app/controllers/metacodes_controller.rb +++ b/app/controllers/metacodes_controller.rb @@ -93,4 +93,11 @@ class MetacodesController < ApplicationController # format.json { head :no_content } # end # end + + private + + # Never trust parameters from the scary internet, only allow the white list through. + def metacode_params + params.require(:metacode).permit(:id, :name, :icon, :color) + end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 0f0be9ba..5fff2f1c 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -7,4 +7,4 @@ class Users::RegistrationsController < Devise::RegistrationsController def after_update_path_for(resource) signed_in_root_path(resource) end -end \ No newline at end of file +end diff --git a/app/controllers/synapses_controller.rb b/app/controllers/synapses_controller.rb index 6ff1537b..40941960 100644 --- a/app/controllers/synapses_controller.rb +++ b/app/controllers/synapses_controller.rb @@ -64,4 +64,10 @@ class SynapsesController < ApplicationController format.json { head :no_content } end end + + private + + def synapse_params + params.require(:synapse).permit(:id, :desc, :category, :weight, :permission, :node1_id, :node2_id, :user_id) + end end diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index ca24d1fb..7929c5ac 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -10,7 +10,6 @@ class TopicsController < ApplicationController @current = current_user term = params[:term] if term && !term.empty? - # !connor term here needs to have .downcase @topics = Topic.where('LOWER("name") like ?', term.downcase + '%').order('"name"') #read this next line as 'delete a topic if its private and you're either @@ -233,4 +232,10 @@ class TopicsController < ApplicationController format.json { head :no_content } end end + + private + + def topic_params + params.require(:topic).permit(:id, :name, :desc, :link, :permission, :user_id, :metacode_id) + end end diff --git a/app/controllers/users/passwords_controller.rb b/app/controllers/users/passwords_controller.rb index d1405f6a..ae5517e8 100644 --- a/app/controllers/users/passwords_controller.rb +++ b/app/controllers/users/passwords_controller.rb @@ -3,4 +3,4 @@ class Users::PasswordsController < Devise::PasswordsController def after_resetting_password_path_for(resource) signed_in_root_path(resource) end -end \ No newline at end of file +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 81ed2122..0141e52d 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -4,10 +4,6 @@ class UsersController < ApplicationController respond_to :html, :json - def user_params - params.require(:user).permit(:name, :email, :image, :password, - :password_confirmation, :code, :joinedwithcode, :remember_me) - # GET /users/1.json def show @user = User.find(params[:id]) @@ -102,4 +98,10 @@ class UsersController < ApplicationController end end + private + + def user_params + params.require(:user).permit(:name, :email, :image, :password, + :password_confirmation, :code, :joinedwithcode, :remember_me) + end From 2f9b09db8693af6990ef6f3183930f60fe3e5a78 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Sat, 19 Sep 2015 16:48:24 +0800 Subject: [PATCH 011/122] fix map/mapping associations that I broke --- app/controllers/application_controller.rb | 2 +- app/models/map.rb | 6 ++++-- app/models/mapping.rb | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 511ab723..0a3d12ab 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -37,7 +37,7 @@ private end def require_admin - unless authenticated? && user.admin + unless authenticated? && admin? redirect_to root_url, notice: "You need to be an admin for that." return false end diff --git a/app/models/map.rb b/app/models/map.rb index dffb9fae..ee949676 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -2,8 +2,10 @@ class Map < ActiveRecord::Base belongs_to :user - has_many :topics, -> { Mapping.topicmapping }, :through => :topicmappings - has_many :synapses, -> { Mapping.synapsemapping }, :through => :synapsemappings + has_many :topicmappings, -> { Mapping.topicmapping }, class_name: :Mapping + has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping + has_many :topics, through: :topicmappings + has_many :synapses, through: :synapsemappings # This method associates the attribute ":image" with a file attachment has_attached_file :screenshot, :styles => { diff --git a/app/models/mapping.rb b/app/models/mapping.rb index 9d6b4947..a8613840 100644 --- a/app/models/mapping.rb +++ b/app/models/mapping.rb @@ -1,7 +1,7 @@ class Mapping < ActiveRecord::Base - scope :topicmapping, -> { where (category: :Topic) } - scope :synapsemapping, -> { where (category: :Synapse) } + scope :topicmapping, -> { where(category: :Topic) } + scope :synapsemapping, -> { where(category: :Synapse) } belongs_to :topic, :class_name => "Topic", :foreign_key => "topic_id" belongs_to :synapse, :class_name => "Synapse", :foreign_key => "synapse_id" From 1aa4d862a14a244b041d18d93d54963574b90ec5 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Sat, 19 Sep 2015 17:01:27 +0800 Subject: [PATCH 012/122] whoo new gems for development. binding.pry is so cool --- Gemfile | 6 ++++++ Gemfile.lock | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Gemfile b/Gemfile index 2ba0e0a9..49533fb2 100644 --- a/Gemfile +++ b/Gemfile @@ -37,6 +37,12 @@ group :production do #this is used on heroku #gem 'rmagick' end +group :development, :test do + gem 'pry-rails' + gem 'better_errors' + gem 'quiet_assets' +end + gem 'jquery-rails' gem 'jquery-ui-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 792c5779..e6f3af2c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -47,12 +47,17 @@ GEM best_in_place (3.0.3) actionpack (>= 3.2) railties (>= 3.2) + better_errors (2.1.1) + coderay (>= 1.0.0) + erubis (>= 2.6.6) + rack (>= 0.9.0) builder (3.2.2) cancan (1.6.10) climate_control (0.0.3) activesupport (>= 3.0) cocaine (0.5.7) climate_control (>= 0.0.3, < 1.0) + coderay (1.1.0) coffee-rails (4.1.0) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) @@ -96,6 +101,7 @@ GEM nokogiri (>= 1.5.9) mail (2.6.3) mime-types (>= 1.16, < 3) + method_source (0.8.2) mime-types (2.6.1) mimemagic (0.3.0) mini_portile (0.6.2) @@ -112,6 +118,14 @@ GEM mime-types mimemagic (= 0.3.0) pg (0.18.3) + pry (0.10.1) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-rails (0.3.4) + pry (>= 0.9.10) + quiet_assets (1.1.0) + railties (>= 3.1, < 5.0) rack (1.6.4) rack-test (0.6.3) rack (>= 1.0) @@ -152,6 +166,7 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) + slop (3.6.0) sprockets (3.3.4) rack (~> 1.0) sprockets-rails (2.3.3) @@ -179,6 +194,7 @@ PLATFORMS DEPENDENCIES aws-sdk best_in_place + better_errors cancan coffee-rails devise @@ -191,6 +207,8 @@ DEPENDENCIES kaminari paperclip pg + pry-rails + quiet_assets rails (= 4.2.4) rails3-jquery-autocomplete redis From 6692c28965204a1a843cafdd45549d6abc323a65 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Sat, 19 Sep 2015 17:08:12 +0800 Subject: [PATCH 013/122] delete_if threw errors, so convert things to arrays for it --- app/controllers/maps_controller.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index d37be5a3..fd2e32b7 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -72,9 +72,9 @@ class MapsController < ApplicationController respond_to do |format| format.html { @allmappers = @map.contributors - @alltopics = @map.topics.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } - @allsynapses = @map.synapses.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } - @allmappings = @map.mappings.delete_if {|m| + @alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } + @allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } + @allmappings = @map.mappings.to_a.delete_if {|m| if m.category == "Synapse" object = m.synapse elsif m.category == "Topic" @@ -100,9 +100,9 @@ class MapsController < ApplicationController end @allmappers = @map.contributors - @alltopics = @map.topics.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } - @allsynapses = @map.synapses.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } - @allmappings = @map.mappings.delete_if {|m| + @alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } + @allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } + @allmappings = @map.mappings.to_a.delete_if {|m| if m.category == "Synapse" object = m.synapse elsif m.category == "Topic" From 31015295f80f5e1554c2dc0f278c49bebd30a9c2 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Sat, 19 Sep 2015 17:11:48 +0800 Subject: [PATCH 014/122] fix best in place deprecation warnings in map info box --- app/views/maps/_mapinfobox.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/maps/_mapinfobox.html.erb b/app/views/maps/_mapinfobox.html.erb index 4305b3a7..134cf8ea 100644 --- a/app/views/maps/_mapinfobox.html.erb +++ b/app/views/maps/_mapinfobox.html.erb @@ -8,7 +8,7 @@ <%= @map && @map.permission != 'private' ? " shareable" : "" %>"> <% if @map %> -
<%= best_in_place @map, :name, :type => :textarea, :activator => "#mapInfoName", :classes => 'best_in_place_name' %>
+
<%= best_in_place @map, :name, :as => :textarea, :activator => "#mapInfoName", :class => 'best_in_place_name' %>
@@ -42,7 +42,7 @@
<% if (authenticated? && @map.authorize_to_edit(user)) || (!authenticated? && @map.desc != "" && @map.desc != nil )%> - <%= best_in_place @map, :desc, :activator => "#mapInfoDesc", :type => :textarea, :nil => "Click to add description...", :classes => 'best_in_place_desc' %> + <%= best_in_place @map, :desc, :activator => "#mapInfoDesc", :as => :textarea, :placeholder => "Click to add description...", :class => 'best_in_place_desc' %> <% end %>
@@ -61,4 +61,4 @@
<% end %> - \ No newline at end of file + From e14001061cfaa0cd0067269d5a7d69d4a8b2c014 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Sat, 19 Sep 2015 20:01:44 +0800 Subject: [PATCH 015/122] fiddle with topic and mapping controllers so they work again --- app/controllers/main_controller.rb | 8 ++++---- app/controllers/mappings_controller.rb | 4 ++-- app/controllers/topics_controller.rb | 18 +++++++++--------- app/controllers/users_controller.rb | 1 + config/application.rb | 2 ++ 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 46747f71..3d2a341f 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -127,7 +127,7 @@ class MainController < ApplicationController end #read this next line as 'delete a topic if its private and you're either 1. logged out or 2. logged in but not the topic creator - @topics.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } + @topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } render json: autocomplete_array_json(@topics) end @@ -163,7 +163,7 @@ class MainController < ApplicationController end #read this next line as 'delete a map if its private and you're either 1. logged out or 2. logged in but not the map creator - @maps.delete_if {|m| m.permission == "private" && (!authenticated? || (authenticated? && @current.id != m.user_id)) } + @maps.to_a.delete_if {|m| m.permission == "private" && (!authenticated? || (authenticated? && @current.id != m.user_id)) } render json: autocomplete_map_array_json(@maps) end @@ -199,7 +199,7 @@ class MainController < ApplicationController # remove any duplicate synapse types that just differ by # leading or trailing whitespaces collectedDesc = [] - @synapses.delete_if {|s| + @synapses.to_a.delete_if {|s| desc = s.desc == nil || s.desc == "" ? "" : s.desc.strip if collectedDesc.index(desc) == nil collectedDesc.push(desc) @@ -221,7 +221,7 @@ class MainController < ApplicationController @synapses.sort! {|s1,s2| s1.desc <=> s2.desc } #read this next line as 'delete a synapse if its private and you're either 1. logged out or 2. logged in but not the synapse creator - @synapses.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } + @synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } render json: autocomplete_synapse_array_json(@synapses) else diff --git a/app/controllers/mappings_controller.rb b/app/controllers/mappings_controller.rb index 4898ceb4..79d8d80a 100644 --- a/app/controllers/mappings_controller.rb +++ b/app/controllers/mappings_controller.rb @@ -13,7 +13,7 @@ class MappingsController < ApplicationController # POST /mappings.json def create - @mapping = Mapping.new(params[:mapping]) + @mapping = Mapping.new(mapping_params) @mapping.map.touch(:updated_at) @@ -30,7 +30,7 @@ class MappingsController < ApplicationController @mapping.map.touch(:updated_at) - if @mapping.update_attributes(params[:mapping]) + if @mapping.update_attributes(mapping_params) head :no_content else render json: @mapping.errors, status: :unprocessable_entity diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 7929c5ac..3cc029f9 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -14,7 +14,7 @@ class TopicsController < ApplicationController #read this next line as 'delete a topic if its private and you're either #1. logged out or 2. logged in but not the topic creator - @topics.delete_if {|t| t.permission == "private" && + @topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } else @topics = [] @@ -34,7 +34,7 @@ class TopicsController < ApplicationController respond_to do |format| format.html { @alltopics = ([@topic] + @topic.relatives).delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } # should limit to topics visible to user - @allsynapses = @topic.synapses.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } + @allsynapses = @topic.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } @allcreators = [] @alltopics.each do |t| @@ -63,8 +63,8 @@ class TopicsController < ApplicationController redirect_to root_url, notice: "Access denied. That topic is private." and return end - @alltopics = @topic.relatives.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } - @allsynapses = @topic.synapses.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } + @alltopics = @topic.relatives.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } + @allsynapses = @topic.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } @allcreators = [] @allcreators.push(@topic.user) @alltopics.each do |t| @@ -100,7 +100,7 @@ class TopicsController < ApplicationController @topicsAlreadyHas = params[:network] ? params[:network].split(',') : [] - @alltopics = @topic.relatives.delete_if {|t| + @alltopics = @topic.relatives.to_a.delete_if {|t| @topicsAlreadyHas.index(t.id.to_s) != nil || (t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id))) } @@ -132,7 +132,7 @@ class TopicsController < ApplicationController @topicsAlreadyHas = params[:network] ? params[:network].split(',') : [] - @alltopics = @topic.relatives.delete_if {|t| + @alltopics = @topic.relatives.to_a.delete_if {|t| @topicsAlreadyHas.index(t.id.to_s) != nil || (params[:metacode] && t.metacode_id.to_s != params[:metacode]) || (t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id))) @@ -140,7 +140,7 @@ class TopicsController < ApplicationController @alltopics.uniq! - @allsynapses = @topic.synapses.delete_if {|s| + @allsynapses = @topic.synapses.to_a.delete_if {|s| (s.topic1 == @topic && @alltopics.index(s.topic2) == nil) || (s.topic2 == @topic && @alltopics.index(s.topic1) == nil) } @@ -171,7 +171,7 @@ class TopicsController < ApplicationController # POST /topics # POST /topics.json def create - @topic = Topic.new(params[:topic]) + @topic = Topic.new(topic_params) respond_to do |format| if @topic.save @@ -188,7 +188,7 @@ class TopicsController < ApplicationController @topic = Topic.find(params[:id]) respond_to do |format| - if @topic.update_attributes(params[:topic]) + if @topic.update_attributes(topic_params) format.json { head :no_content } else format.json { render json: @topic.errors, status: :unprocessable_entity } diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0141e52d..52996f49 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -103,5 +103,6 @@ class UsersController < ApplicationController def user_params params.require(:user).permit(:name, :email, :image, :password, :password_confirmation, :code, :joinedwithcode, :remember_me) + end end diff --git a/config/application.rb b/config/application.rb index 0a5e6b51..f9e0a87d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -44,5 +44,7 @@ module Metamaps # Version of your assets, change this if you want to expire all your assets config.assets.version = '2.0' + + config.active_record.raise_in_transactional_callbacks = true end end From 62035ed015e8a2109ffd22106a39dfc487ff62d0 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Sat, 19 Sep 2015 20:05:36 +0800 Subject: [PATCH 016/122] fix other controllers needing create/update changed for rails 4 --- app/controllers/maps_controller.rb | 2 +- app/controllers/metacode_sets_controller.rb | 4 ++-- app/controllers/metacodes_controller.rb | 4 ++-- app/controllers/synapses_controller.rb | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index fd2e32b7..c60b8831 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -180,7 +180,7 @@ class MapsController < ApplicationController respond_to do |format| if !@map format.json { render json: "unauthorized" } - elsif @map.update_attributes(params[:map]) + elsif @map.update_attributes(map_params) format.json { head :no_content } else format.json { render json: @map.errors, status: :unprocessable_entity } diff --git a/app/controllers/metacode_sets_controller.rb b/app/controllers/metacode_sets_controller.rb index 376babe5..720076c1 100644 --- a/app/controllers/metacode_sets_controller.rb +++ b/app/controllers/metacode_sets_controller.rb @@ -45,7 +45,7 @@ class MetacodeSetsController < ApplicationController # POST /metacode_sets.json def create @user = current_user - @metacode_set = MetacodeSet.new(params[:metacode_set]) + @metacode_set = MetacodeSet.new(metacode_set_params) @metacode_set.user_id = @user.id respond_to do |format| @@ -70,7 +70,7 @@ class MetacodeSetsController < ApplicationController @metacode_set = MetacodeSet.find(params[:id]) respond_to do |format| - if @metacode_set.update_attributes(params[:metacode_set]) + if @metacode_set.update_attributes(metacode_set_params) # build an array of the IDs of the metacodes currently in the set @currentMetacodes = @metacode_set.metacodes.map{ |m| m.id.to_s } diff --git a/app/controllers/metacodes_controller.rb b/app/controllers/metacodes_controller.rb index 25a7c096..1e5049f1 100644 --- a/app/controllers/metacodes_controller.rb +++ b/app/controllers/metacodes_controller.rb @@ -51,7 +51,7 @@ class MetacodesController < ApplicationController # POST /metacodes # POST /metacodes.json def create - @metacode = Metacode.new(params[:metacode]) + @metacode = Metacode.new(metacode_params) respond_to do |format| if @metacode.save @@ -70,7 +70,7 @@ class MetacodesController < ApplicationController @metacode = Metacode.find(params[:id]) respond_to do |format| - if @metacode.update_attributes(params[:metacode]) + if @metacode.update_attributes(metacode_params) format.html { redirect_to metacodes_url, notice: 'Metacode was successfully updated.' } format.json { head :no_content } else diff --git a/app/controllers/synapses_controller.rb b/app/controllers/synapses_controller.rb index 40941960..3782d336 100644 --- a/app/controllers/synapses_controller.rb +++ b/app/controllers/synapses_controller.rb @@ -21,7 +21,7 @@ class SynapsesController < ApplicationController # POST /synapses # POST /synapses.json def create - @synapse = Synapse.new(params[:synapse]) + @synapse = Synapse.new(synapse_params) respond_to do |format| if @synapse.save @@ -38,7 +38,7 @@ class SynapsesController < ApplicationController @synapse = Synapse.find(params[:id]) respond_to do |format| - if @synapse.update_attributes(params[:synapse]) + if @synapse.update_attributes(synapse_params) format.json { head :no_content } else format.json { render json: @synapse.errors, status: :unprocessable_entity } From 086acb09af5d2c634c74a53c90d3c3b5181fcf75 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Sat, 19 Sep 2015 20:17:56 +0800 Subject: [PATCH 017/122] select metacodes with one query instead of n queries, move logic to application_helper --- app/helpers/application_helper.rb | 16 ++++++++++++++++ app/models/metacode.rb | 3 +-- app/views/maps/_newtopic.html.erb | 13 ++----------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 95f6ba29..fa7876f5 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,18 @@ module ApplicationHelper + def get_metacodeset + @m = user.settings.metacodes + set = @m[0].include?("metacodeset") ? MetacodeSet.find(@m[0].sub("metacodeset-","").to_i) : false + return set + end + + def user_metacodes + @m = user.settings.metacodes + set = get_metacodeset + if set + @metacodes = set.metacodes + else + @metacodes = Metacode.where(id: @m).to_a + end + @metacodes.sort! {|m1,m2| m2.name.downcase <=> m1.name.downcase }.rotate!(-1) + end end diff --git a/app/models/metacode.rb b/app/models/metacode.rb index 315f7800..03b0f0c0 100644 --- a/app/models/metacode.rb +++ b/app/models/metacode.rb @@ -13,5 +13,4 @@ class Metacode < ActiveRecord::Base return true if self.metacode_sets.include? metacode_set return false end - -end \ No newline at end of file +end diff --git a/app/views/maps/_newtopic.html.erb b/app/views/maps/_newtopic.html.erb index f22920bb..16b5fecb 100644 --- a/app/views/maps/_newtopic.html.erb +++ b/app/views/maps/_newtopic.html.erb @@ -1,17 +1,8 @@ <%= form_for Topic.new, url: topics_url, remote: true do |form| %>
- <% @m = user.settings.metacodes %> - <% set = @m[0].include?("metacodeset") ? MetacodeSet.find(@m[0].sub("metacodeset-","").to_i) : false %> - <% if set %> - <% @metacodes = set.metacodes %> - <% else %> - <% @metacodes = [] %> - <% @m.each do |m| %> - <% @metacodes.push(Metacode.find(m.to_i)) %> - <% end %> - <% end %> - <% @metacodes.sort! {|m1,m2| m2.name.downcase <=> m1.name.downcase }.rotate!(-1) %> + <% @metacodes = user_metacodes() %> + <% set = get_metacodeset() %> <% @metacodes.each do |metacode| %> <%= metacode.name %> <% end %> From b2e15c648316185da748ea725970a03a16fdc920 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Sat, 19 Sep 2015 20:25:07 +0800 Subject: [PATCH 018/122] cancan => cancancan --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 49533fb2..7649f497 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ gem 'rails', '4.2.4' gem 'devise' gem 'redis' gem 'pg' -gem 'cancan' +gem 'cancancan' gem 'formula' gem 'formtastic' gem 'json' diff --git a/Gemfile.lock b/Gemfile.lock index e6f3af2c..0c82cb5a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,7 +52,7 @@ GEM erubis (>= 2.6.6) rack (>= 0.9.0) builder (3.2.2) - cancan (1.6.10) + cancancan (1.12.0) climate_control (0.0.3) activesupport (>= 3.0) cocaine (0.5.7) @@ -195,7 +195,7 @@ DEPENDENCIES aws-sdk best_in_place better_errors - cancan + cancancan coffee-rails devise formtastic From 1fbd16ac95bde1cdd3817771779b34a5bbe9aed2 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Sat, 19 Sep 2015 20:46:10 +0800 Subject: [PATCH 019/122] fix problem with join route --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 3c79f881..e80b837a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -34,7 +34,7 @@ Metamaps::Application.routes.draw do get 'login' => 'devise/sessions#new', :as => :new_user_session post 'login' => 'devise/sessions#create', :as => :user_session get 'logout' => 'devise/sessions#destroy', :as => :destroy_user_session -# get 'join' => 'devise/registrations#new', :as => :new_user_registration + get 'join' => 'devise/registrations#new', :as => :new_user_registration_path end get 'users/:id/details', to: 'users#details', as: :details From 869fb816e9fce94588e6034908be592461b26834 Mon Sep 17 00:00:00 2001 From: Connor Turland Date: Sat, 19 Sep 2015 13:16:07 -0400 Subject: [PATCH 020/122] this enables the vagrant port forwarding --- Gemfile.lock | 3 --- config/boot.rb | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0c82cb5a..5cec997c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -215,6 +215,3 @@ DEPENDENCIES sass-rails uglifier uservoice-ruby - -BUNDLED WITH - 1.10.6 diff --git a/config/boot.rb b/config/boot.rb index 4489e586..0f0d7c60 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,4 +1,12 @@ require 'rubygems' +require 'rails/commands/server' +module Rails + class Server + def default_options + super.merge(Host: '0.0.0.0', Port: 3000) + end + end +end # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) From f5dbfd5d720a3cfc7d560ea1a338e5eb974bf7b8 Mon Sep 17 00:00:00 2001 From: Connor Turland Date: Sat, 19 Sep 2015 13:25:30 -0400 Subject: [PATCH 021/122] configuration for production environments like heroku --- Gemfile | 1 + Gemfile.lock | 6 ++++++ config/environments/development.rb | 1 + config/environments/production.rb | 5 ++++- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 7649f497..1c678383 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,7 @@ end group :production do #this is used on heroku #gem 'rmagick' + gem 'rails_12factor' end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 5cec997c..af773f25 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -150,6 +150,11 @@ GEM loofah (~> 2.0) rails3-jquery-autocomplete (1.0.15) rails (>= 3.2) + rails_12factor (0.0.3) + rails_serve_static_assets + rails_stdout_logging + rails_serve_static_assets (0.0.4) + rails_stdout_logging (0.0.4) railties (4.2.4) actionpack (= 4.2.4) activesupport (= 4.2.4) @@ -211,6 +216,7 @@ DEPENDENCIES quiet_assets rails (= 4.2.4) rails3-jquery-autocomplete + rails_12factor redis sass-rails uglifier diff --git a/config/environments/development.rb b/config/environments/development.rb index 251e12ab..53e8b4fb 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,6 +1,7 @@ Metamaps::Application.configure do # Settings specified here will take precedence over those in config/application.rb + config.log_level = :info config.eager_load = false # In the development environment your application's code is reloaded on diff --git a/config/environments/production.rb b/config/environments/production.rb index 17773071..a515b4ca 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,6 +1,7 @@ Metamaps::Application.configure do # Settings specified here will take precedence over those in config/application.rb + config.log_level = :warn config.eager_load = true config.assets.js_compressor = :uglifier @@ -12,7 +13,9 @@ Metamaps::Application.configure do config.action_controller.perform_caching = true # Disable Rails's static asset server (Apache or nginx will already do this) - config.serve_static_assets = false + config.serve_static_files = true + + config.assets.compile = true # Compress JavaScripts and CSS config.assets.compress = true From 8f8f40c0e4d583ce26f05abbb41576c71098633d Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Tue, 22 Sep 2015 22:27:34 +0800 Subject: [PATCH 022/122] replace the obvious spots with asset_path --- app/views/layouts/_templates.html.erb | 6 +++--- app/views/main/home.html.erb | 2 +- app/views/maps/_mapinfobox.html.erb | 2 +- app/views/metacode_sets/_form.html.erb | 2 +- app/views/metacode_sets/index.html.erb | 2 +- app/views/metacodes/index.html.erb | 2 +- app/views/shared/_cheatsheet.html.erb | 2 +- app/views/shared/_filterBox.html.erb | 2 +- app/views/shared/_metacodeoptions.html.erb | 2 +- app/views/shared/_switchmetacodes.html.erb | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/views/layouts/_templates.html.erb b/app/views/layouts/_templates.html.erb index afd9de52..4762a271 100644 --- a/app/views/layouts/_templates.html.erb +++ b/app/views/layouts/_templates.html.erb @@ -129,7 +129,7 @@ -
\ No newline at end of file + diff --git a/app/views/main/home.html.erb b/app/views/main/home.html.erb index 2590f2f5..b40fb2a4 100644 --- a/app/views/main/home.html.erb +++ b/app/views/main/home.html.erb @@ -50,4 +50,4 @@ Metamaps.GlobalUI.Search.isOpen = true; Metamaps.GlobalUI.Search.lock(); -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/maps/_mapinfobox.html.erb b/app/views/maps/_mapinfobox.html.erb index 134cf8ea..43381835 100644 --- a/app/views/maps/_mapinfobox.html.erb +++ b/app/views/maps/_mapinfobox.html.erb @@ -13,7 +13,7 @@
<% if @map.contributors.count == 0 %> - + <% elsif @map.contributors.count == 1 %> <% elsif @map.contributors.count == 2 %> diff --git a/app/views/metacode_sets/_form.html.erb b/app/views/metacode_sets/_form.html.erb index 3a5089b9..e3d1ae40 100644 --- a/app/views/metacode_sets/_form.html.erb +++ b/app/views/metacode_sets/_form.html.erb @@ -86,4 +86,4 @@ { :class => 'button', 'data-bypass' => 'true' } %> <%= f.submit :class => 'add', :onclick => "return Metamaps.Admin.validate();" %>
-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/metacode_sets/index.html.erb b/app/views/metacode_sets/index.html.erb index 7a93d97f..ba83b0a3 100644 --- a/app/views/metacode_sets/index.html.erb +++ b/app/views/metacode_sets/index.html.erb @@ -34,4 +34,4 @@ <% end %>
- \ No newline at end of file + diff --git a/app/views/metacodes/index.html.erb b/app/views/metacodes/index.html.erb index c99634d4..1ebdb5da 100644 --- a/app/views/metacodes/index.html.erb +++ b/app/views/metacodes/index.html.erb @@ -28,4 +28,4 @@ <% end %> - \ No newline at end of file + diff --git a/app/views/shared/_cheatsheet.html.erb b/app/views/shared/_cheatsheet.html.erb index f0a2ffbc..0af70ec1 100644 --- a/app/views/shared/_cheatsheet.html.erb +++ b/app/views/shared/_cheatsheet.html.erb @@ -61,7 +61,7 @@ Change Topic permission: Click on 'Permission' icon (only for topic creator)
- Open Topic view: Click on icon within topic card bar + Open Topic view: Click on icon within topic card bar
Close 'Topic' card: Click on canvas diff --git a/app/views/shared/_filterBox.html.erb b/app/views/shared/_filterBox.html.erb index b01f6eca..714b1378 100644 --- a/app/views/shared/_filterBox.html.erb +++ b/app/views/shared/_filterBox.html.erb @@ -76,7 +76,7 @@ @synapses.each_with_index do |synapse, index| d = synapse.desc || "" @synapselist += '
  • ' - @synapselist += 'synapse icon

    ' + d + @synapselist += 'synapse icon

    ' + d @synapselist += '

  • ' end @mappers.each_with_index do |mapper, index| diff --git a/app/views/shared/_metacodeoptions.html.erb b/app/views/shared/_metacodeoptions.html.erb index f45a1aa6..25aae79e 100644 --- a/app/views/shared/_metacodeoptions.html.erb +++ b/app/views/shared/_metacodeoptions.html.erb @@ -34,4 +34,4 @@ -
    \ No newline at end of file + diff --git a/app/views/shared/_switchmetacodes.html.erb b/app/views/shared/_switchmetacodes.html.erb index fb3962ef..3d683198 100644 --- a/app/views/shared/_switchmetacodes.html.erb +++ b/app/views/shared/_switchmetacodes.html.erb @@ -73,4 +73,4 @@ \ No newline at end of file + From b9140e9b8ddcc5194210cc3de597d35f05dbfc50 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Mon, 28 Sep 2015 14:43:09 +0800 Subject: [PATCH 023/122] add qa steps file first draft --- metamaps-qa-steps.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 metamaps-qa-steps.txt diff --git a/metamaps-qa-steps.txt b/metamaps-qa-steps.txt new file mode 100644 index 00000000..36ea193a --- /dev/null +++ b/metamaps-qa-steps.txt @@ -0,0 +1,24 @@ +Metamaps Test Suite + +1) Log in to the interface +2) Create an account using your join code +3) Check your user's "generation" +4) Create three maps: private, public, and another public +5) Change the last map's permissions to commons +6) Change a map's name +7) Create a topic on map #1 +8) Verify (in a private window or another browser) that the second user can't acccess map #1 +9) Create a topic on map #2 +10) Verify that the second user can't edit map #2 +11) Create a topic on map #3 +12) Verify that the second can edit map #3 +13) Pull a topic from map #1 to map #3 +14) Create a private topic on map #1 +15) Verify that the private topic can be pulled from map #1 by the same user +16) Verify that the private topic can't be pulled from map #1 by another user +17) Login as admin. Change metacode sets. +18) Add a number of topics to one of your maps. Reload to see if they are still there. +19) Add a number of synapses to one of your maps. Reload to see if they are still there. +20) Rearrange one of your maps. Reload to see if the layout is preserved. +21) Set the screenshot for one of your maps, and verify the index of maps is updated. +22) Open two browsers on map #3 and verify that realtime editing works. From bb29e0e724167773209ad0852b709662156d4eaa Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 1 Oct 2015 11:02:03 +0800 Subject: [PATCH 024/122] fiddle with Gemfile --- Gemfile | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 406c2b84..0aa716f8 100644 --- a/Gemfile +++ b/Gemfile @@ -19,6 +19,10 @@ gem 'dotenv' gem 'paperclip' gem 'aws-sdk' +gem 'jquery-rails' +gem 'jquery-ui-rails' +gem 'jbuilder' + #gem 'therubyracer' #optional #gem 'rb-readline' @@ -44,9 +48,3 @@ group :development, :test do gem 'better_errors' gem 'quiet_assets' end - -gem 'jquery-rails' -gem 'jquery-ui-rails' - -# To use Jbuilder templates for JSON -gem 'jbuilder' From 9e43cc2e968f221447ccef60e46266fe445dd359 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 1 Oct 2015 11:02:39 +0800 Subject: [PATCH 025/122] dependent destroy models for topics/maps/synapses on mappings --- app/controllers/maps_controller.rb | 10 +-------- app/controllers/synapses_controller.rb | 10 +-------- app/controllers/topics_controller.rb | 28 +------------------------- app/models/map.rb | 4 ++-- app/models/synapse.rb | 2 +- app/models/topic.rb | 6 +++--- 6 files changed, 9 insertions(+), 51 deletions(-) diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index c60b8831..21946f88 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -218,15 +218,7 @@ class MapsController < ApplicationController @map = Map.find(params[:id]).authorize_to_delete(@current) - if @map - @mappings = @map.mappings - - @mappings.each do |mapping| - mapping.delete - end - - @map.delete - end + @map.delete if @map respond_to do |format| format.json { diff --git a/app/controllers/synapses_controller.rb b/app/controllers/synapses_controller.rb index 3782d336..47d3321e 100644 --- a/app/controllers/synapses_controller.rb +++ b/app/controllers/synapses_controller.rb @@ -50,15 +50,7 @@ class SynapsesController < ApplicationController def destroy @current = current_user @synapse = Synapse.find(params[:id]).authorize_to_delete(@current) - - if @synapse - @synapse.mappings.each do |m| - m.map.touch(:updated_at) - m.delete - end - - @synapse.delete - end + @synapse.delete if @synapse respond_to do |format| format.json { head :no_content } diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 3cc029f9..d73be190 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -200,33 +200,7 @@ class TopicsController < ApplicationController def destroy @current = current_user @topic = Topic.find(params[:id]).authorize_to_delete(@current) - - if @topic - @synapses = @topic.synapses - @mappings = @topic.mappings - - @synapses.each do |synapse| - synapse.mappings.each do |m| - - @map = m.map - @map.touch(:updated_at) - - m.delete - end - - synapse.delete - end - - @mappings.each do |mapping| - - @map = mapping.map - @map.touch(:updated_at) - - mapping.delete - end - - @topic.delete - end + @topic.delete if @topic respond_to do |format| format.json { head :no_content } diff --git a/app/models/map.rb b/app/models/map.rb index ee949676..6262924e 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -2,8 +2,8 @@ class Map < ActiveRecord::Base belongs_to :user - has_many :topicmappings, -> { Mapping.topicmapping }, class_name: :Mapping - has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping + has_many :topicmappings, -> { Mapping.topicmapping }, class_name: :Mapping, dependent: :destroy + has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping, dependent: :destroy has_many :topics, through: :topicmappings has_many :synapses, through: :synapsemappings diff --git a/app/models/synapse.rb b/app/models/synapse.rb index bf3bdab2..10fba6e9 100644 --- a/app/models/synapse.rb +++ b/app/models/synapse.rb @@ -5,7 +5,7 @@ class Synapse < ActiveRecord::Base belongs_to :topic1, :class_name => "Topic", :foreign_key => "node1_id" belongs_to :topic2, :class_name => "Topic", :foreign_key => "node2_id" - has_many :mappings + has_many :mappings, dependent: :destroy has_many :maps, :through => :mappings def user_name diff --git a/app/models/topic.rb b/app/models/topic.rb index 078c633e..d28127fa 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -3,12 +3,12 @@ class Topic < ActiveRecord::Base belongs_to :user - has_many :synapses1, :class_name => 'Synapse', :foreign_key => 'node1_id' - has_many :synapses2, :class_name => 'Synapse', :foreign_key => 'node2_id' + has_many :synapses1, :class_name => 'Synapse', :foreign_key => 'node1_id', dependent: :destroy + has_many :synapses2, :class_name => 'Synapse', :foreign_key => 'node2_id', dependent: :destroy has_many :topics1, :through => :synapses2, :source => :topic1 has_many :topics2, :through => :synapses1, :source => :topic2 - has_many :mappings + has_many :mappings, dependent: :destroy has_many :maps, :through => :mappings # This method associates the attribute ":image" with a file attachment From 347d77df824362b14770eede6cc3ab21ee4d6f07 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 1 Oct 2015 11:14:25 +0800 Subject: [PATCH 026/122] upgrade typeahead to allow new syntax --- app/assets/javascripts/lib/typeahead.js | 2439 +++++++++++++---------- app/assets/javascripts/src/Metamaps.js | 8 +- 2 files changed, 1410 insertions(+), 1037 deletions(-) diff --git a/app/assets/javascripts/lib/typeahead.js b/app/assets/javascripts/lib/typeahead.js index 3a413d68..2b089289 100644 --- a/app/assets/javascripts/lib/typeahead.js +++ b/app/assets/javascripts/lib/typeahead.js @@ -1,659 +1,608 @@ /*! - * typeahead.js 0.9.3 - * https://github.com/twitter/typeahead - * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT + * typeahead.js 0.11.1 + * https://github.com/twitter/typeahead.js + * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT */ -(function($) { - var VERSION = "0.9.3"; - var utils = { - isMsie: function() { - var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); - return match ? parseInt(match[2], 10) : false; - }, - isBlankString: function(str) { - return !str || /^\s*$/.test(str); - }, - escapeRegExChars: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - isString: function(obj) { - return typeof obj === "string"; - }, - isNumber: function(obj) { - return typeof obj === "number"; - }, - isArray: $.isArray, - isFunction: $.isFunction, - isObject: $.isPlainObject, - isUndefined: function(obj) { - return typeof obj === "undefined"; - }, - bind: $.proxy, - bindAll: function(obj) { - var val; - for (var key in obj) { - $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); - } - }, - indexOf: function(haystack, needle) { - for (var i = 0; i < haystack.length; i++) { - if (haystack[i] === needle) { - return i; +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define("typeahead.js", [ "jquery" ], function(a0) { + return factory(a0); + }); + } else if (typeof exports === "object") { + module.exports = factory(require("jquery")); + } else { + factory(jQuery); + } +})(this, function($) { + var _ = function() { + "use strict"; + return { + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isJQuery: function(obj) { + return obj instanceof $; + }, + toStr: function toStr(s) { + return _.isUndefined(s) || s === null ? "" : s + ""; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); } - } - return -1; - }, - each: $.each, - map: $.map, - filter: $.grep, - every: function(obj, test) { - var result = true; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (!(result = test.call(null, val, key, obj))) { - return false; + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; } - }); - return !!result; - }, - some: function(obj, test) { - var result = false; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (result = test.call(null, val, key, obj)) { - return false; + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; } - }); - return !!result; - }, - mixin: $.extend, - getUniqueId: function() { - var counter = 0; - return function() { - return counter++; - }; - }(), - defer: function(fn) { - setTimeout(fn, 0); - }, - debounce: function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments, later, callNow; - later = function() { - timeout = null; - if (!immediate) { + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + identity: function(x) { + return x; + }, + clone: function(obj) { + return $.extend(true, {}, obj); + }, + getIdGenerator: function() { + var counter = 0; + return function() { + return counter++; + }; + }, + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { result = func.apply(context, args); } + return result; }; - callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - } - return result; - }; - }, - throttle: function(func, wait) { - var context, args, timeout, result, previous, later; - previous = 0; - later = function() { - previous = new Date(); - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = new Date(), remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - tokenizeQuery: function(str) { - return $.trim(str).toLowerCase().split(/[\s]+/); - }, - tokenizeText: function(str) { - return $.trim(str).toLowerCase().split(/[\s\-_]+/); - }, - getProtocol: function() { - return location.protocol; - }, - noop: function() {} - }; - var EventTarget = function() { - var eventSplitter = /\s+/; - return { - on: function(events, callback) { - var event; - if (!callback) { - return this; - } - this._callbacks = this._callbacks || {}; - events = events.split(eventSplitter); - while (event = events.shift()) { - this._callbacks[event] = this._callbacks[event] || []; - this._callbacks[event].push(callback); - } - return this; }, - trigger: function(events, data) { - var event, callbacks; - if (!this._callbacks) { - return this; - } - events = events.split(eventSplitter); - while (event = events.shift()) { - if (callbacks = this._callbacks[event]) { - for (var i = 0; i < callbacks.length; i += 1) { - callbacks[i].call(this, { - type: event, - data: data - }); - } + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); } - } - return this; - } + return result; + }; + }, + stringify: function(val) { + return _.isString(val) ? val : JSON.stringify(val); + }, + noop: function() {} }; }(); + var WWW = function() { + "use strict"; + var defaultClassNames = { + wrapper: "twitter-typeahead", + input: "tt-input", + hint: "tt-hint", + menu: "tt-menu", + dataset: "tt-dataset", + suggestion: "tt-suggestion", + selectable: "tt-selectable", + empty: "tt-empty", + open: "tt-open", + cursor: "tt-cursor", + highlight: "tt-highlight" + }; + return build; + function build(o) { + var www, classes; + classes = _.mixin({}, defaultClassNames, o); + www = { + css: buildCss(), + classes: classes, + html: buildHtml(classes), + selectors: buildSelectors(classes) + }; + return { + css: www.css, + html: www.html, + classes: www.classes, + selectors: www.selectors, + mixin: function(o) { + _.mixin(o, www); + } + }; + } + function buildHtml(c) { + return { + wrapper: '', + menu: '
    ' + }; + } + function buildSelectors(classes) { + var selectors = {}; + _.each(classes, function(v, k) { + selectors[k] = "." + v; + }); + return selectors; + } + function buildCss() { + var css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none", + opacity: "1" + }, + input: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + inputWithNoHint: { + position: "relative", + verticalAlign: "top" + }, + menu: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + }, + ltr: { + left: "0", + right: "auto" + }, + rtl: { + left: "auto", + right: " 0" + } + }; + if (_.isMsie()) { + _.mixin(css.input, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + return css; + } + }(); var EventBus = function() { - var namespace = "typeahead:"; + "use strict"; + var namespace, deprecationMap; + namespace = "typeahead:"; + deprecationMap = { + render: "rendered", + cursorchange: "cursorchanged", + select: "selected", + autocomplete: "autocompleted" + }; function EventBus(o) { if (!o || !o.el) { $.error("EventBus initialized without el"); } this.$el = $(o.el); } - utils.mixin(EventBus.prototype, { + _.mixin(EventBus.prototype, { + _trigger: function(type, args) { + var $e; + $e = $.Event(namespace + type); + (args = args || []).unshift($e); + this.$el.trigger.apply(this.$el, args); + return $e; + }, + before: function(type) { + var args, $e; + args = [].slice.call(arguments, 1); + $e = this._trigger("before" + type, args); + return $e.isDefaultPrevented(); + }, trigger: function(type) { - var args = [].slice.call(arguments, 1); - this.$el.trigger(namespace + type, args); + var deprecatedType; + this._trigger(type, [].slice.call(arguments, 1)); + if (deprecatedType = deprecationMap[type]) { + this._trigger(deprecatedType, [].slice.call(arguments, 1)); + } } }); return EventBus; }(); - var PersistentStorage = function() { - var ls, methods; - try { - ls = window.localStorage; - ls.setItem("~~~", "!"); - ls.removeItem("~~~"); - } catch (err) { - ls = null; - } - function PersistentStorage(namespace) { - this.prefix = [ "__", namespace, "__" ].join(""); - this.ttlKey = "__ttl__"; - this.keyMatcher = new RegExp("^" + this.prefix); - } - if (ls && window.JSON) { - methods = { - _prefix: function(key) { - return this.prefix + key; - }, - _ttlKey: function(key) { - return this._prefix(key) + this.ttlKey; - }, - get: function(key) { - if (this.isExpired(key)) { - this.remove(key); - } - return decode(ls.getItem(this._prefix(key))); - }, - set: function(key, val, ttl) { - if (utils.isNumber(ttl)) { - ls.setItem(this._ttlKey(key), encode(now() + ttl)); - } else { - ls.removeItem(this._ttlKey(key)); - } - return ls.setItem(this._prefix(key), encode(val)); - }, - remove: function(key) { - ls.removeItem(this._ttlKey(key)); - ls.removeItem(this._prefix(key)); - return this; - }, - clear: function() { - var i, key, keys = [], len = ls.length; - for (i = 0; i < len; i++) { - if ((key = ls.key(i)).match(this.keyMatcher)) { - keys.push(key.replace(this.keyMatcher, "")); - } - } - for (i = keys.length; i--; ) { - this.remove(keys[i]); - } - return this; - }, - isExpired: function(key) { - var ttl = decode(ls.getItem(this._ttlKey(key))); - return utils.isNumber(ttl) && now() > ttl ? true : false; - } - }; - } else { - methods = { - get: utils.noop, - set: utils.noop, - remove: utils.noop, - clear: utils.noop, - isExpired: utils.noop - }; - } - utils.mixin(PersistentStorage.prototype, methods); - return PersistentStorage; - function now() { - return new Date().getTime(); - } - function encode(val) { - return JSON.stringify(utils.isUndefined(val) ? null : val); - } - function decode(val) { - return JSON.parse(val); - } - }(); - var RequestCache = function() { - function RequestCache(o) { - utils.bindAll(this); - o = o || {}; - this.sizeLimit = o.sizeLimit || 10; - this.cache = {}; - this.cachedKeysByAge = []; - } - utils.mixin(RequestCache.prototype, { - get: function(url) { - return this.cache[url]; - }, - set: function(url, resp) { - var requestToEvict; - if (this.cachedKeysByAge.length === this.sizeLimit) { - requestToEvict = this.cachedKeysByAge.shift(); - delete this.cache[requestToEvict]; - } - this.cache[url] = resp; - this.cachedKeysByAge.push(url); - } - }); - return RequestCache; - }(); - var Transport = function() { - var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; - function Transport(o) { - utils.bindAll(this); - o = utils.isString(o) ? { - url: o - } : o; - requestCache = requestCache || new RequestCache(); - maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; - this.url = o.url; - this.wildcard = o.wildcard || "%QUERY"; - this.filter = o.filter; - this.replace = o.replace; - this.ajaxSettings = { - type: "get", - cache: o.cache, - timeout: o.timeout, - dataType: o.dataType || "json", - beforeSend: o.beforeSend - }; - this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); - } - utils.mixin(Transport.prototype, { - _get: function(url, cb) { - var that = this; - if (belowPendingRequestsThreshold()) { - this._sendRequest(url).done(done); - } else { - this.onDeckRequestArgs = [].slice.call(arguments, 0); - } - function done(resp) { - var data = that.filter ? that.filter(resp) : resp; - cb && cb(data); - requestCache.set(url, resp); - } - }, - _sendRequest: function(url) { - var that = this, jqXhr = pendingRequests[url]; - if (!jqXhr) { - incrementPendingRequests(); - jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); - } - return jqXhr; - function always() { - decrementPendingRequests(); - pendingRequests[url] = null; - if (that.onDeckRequestArgs) { - that._get.apply(that, that.onDeckRequestArgs); - that.onDeckRequestArgs = null; - } - } - }, - get: function(query, cb) { - var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; - cb = cb || utils.noop; - url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); - if (resp = requestCache.get(url)) { - utils.defer(function() { - cb(that.filter ? that.filter(resp) : resp); - }); - } else { - this._get(url, cb); - } - return !!resp; - } - }); - return Transport; - function incrementPendingRequests() { - pendingRequestsCount++; - } - function decrementPendingRequests() { - pendingRequestsCount--; - } - function belowPendingRequestsThreshold() { - return pendingRequestsCount < maxPendingRequests; - } - }(); - var Dataset = function() { - var keys = { - thumbprint: "thumbprint", - protocol: "protocol", - itemHash: "itemHash", - adjacencyList: "adjacencyList" + var EventEmitter = function() { + "use strict"; + var splitter = /\s+/, nextTick = getNextTick(); + return { + onSync: onSync, + onAsync: onAsync, + off: off, + trigger: trigger }; - function Dataset(o) { - utils.bindAll(this); - if (utils.isString(o.template) && !o.engine) { - $.error("no template engine specified"); + function on(method, types, cb, context) { + var type; + if (!cb) { + return this; } - if (!o.local && !o.prefetch && !o.remote) { - $.error("one of local, prefetch, or remote is required"); + types = types.split(splitter); + cb = context ? bindContext(cb, context) : cb; + this._callbacks = this._callbacks || {}; + while (type = types.shift()) { + this._callbacks[type] = this._callbacks[type] || { + sync: [], + async: [] + }; + this._callbacks[type][method].push(cb); } - this.name = o.name || utils.getUniqueId(); - this.limit = o.limit || 5; - this.minLength = o.minLength || 1; - this.header = o.header; - this.footer = o.footer; - this.valueKey = o.valueKey || "value"; - this.template = compileTemplate(o.template, o.engine, this.valueKey); - this.local = o.local; - this.prefetch = o.prefetch; - this.remote = o.remote; - this.itemHash = {}; - this.adjacencyList = {}; - this.storage = o.name ? new PersistentStorage(o.name) : null; + return this; } - utils.mixin(Dataset.prototype, { - _processLocalData: function(data) { - this._mergeProcessedData(this._processData(data)); - }, - _loadPrefetchData: function(o) { - var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; - if (this.storage) { - storedThumbprint = this.storage.get(keys.thumbprint); - storedProtocol = this.storage.get(keys.protocol); - storedItemHash = this.storage.get(keys.itemHash); - storedAdjacencyList = this.storage.get(keys.adjacencyList); - } - isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); - o = utils.isString(o) ? { - url: o - } : o; - o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; - if (storedItemHash && storedAdjacencyList && !isExpired) { - this._mergeProcessedData({ - itemHash: storedItemHash, - adjacencyList: storedAdjacencyList - }); - deferred = $.Deferred().resolve(); - } else { - deferred = $.getJSON(o.url).done(processPrefetchData); - } - return deferred; - function processPrefetchData(data) { - var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; - if (that.storage) { - that.storage.set(keys.itemHash, itemHash, o.ttl); - that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); - that.storage.set(keys.thumbprint, thumbprint, o.ttl); - that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); - } - that._mergeProcessedData(processedData); - } - }, - _transformDatum: function(datum) { - var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { - value: value, - tokens: tokens - }; - if (utils.isString(datum)) { - item.datum = {}; - item.datum[this.valueKey] = datum; - } else { - item.datum = datum; - } - item.tokens = utils.filter(item.tokens, function(token) { - return !utils.isBlankString(token); - }); - item.tokens = utils.map(item.tokens, function(token) { - return token.toLowerCase(); - }); - return item; - }, - _processData: function(data) { - var that = this, itemHash = {}, adjacencyList = {}; - utils.each(data, function(i, datum) { - var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); - itemHash[id] = item; - utils.each(item.tokens, function(i, token) { - var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); - !~utils.indexOf(adjacency, id) && adjacency.push(id); - }); - }); - return { - itemHash: itemHash, - adjacencyList: adjacencyList - }; - }, - _mergeProcessedData: function(processedData) { - var that = this; - utils.mixin(this.itemHash, processedData.itemHash); - utils.each(processedData.adjacencyList, function(character, adjacency) { - var masterAdjacency = that.adjacencyList[character]; - that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; - }); - }, - _getLocalSuggestions: function(terms) { - var that = this, firstChars = [], lists = [], shortestList, suggestions = []; - utils.each(terms, function(i, term) { - var firstChar = term.charAt(0); - !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); - }); - utils.each(firstChars, function(i, firstChar) { - var list = that.adjacencyList[firstChar]; - if (!list) { - return false; - } - lists.push(list); - if (!shortestList || list.length < shortestList.length) { - shortestList = list; - } - }); - if (lists.length < firstChars.length) { - return []; - } - utils.each(shortestList, function(i, id) { - var item = that.itemHash[id], isCandidate, isMatch; - isCandidate = utils.every(lists, function(list) { - return ~utils.indexOf(list, id); - }); - isMatch = isCandidate && utils.every(terms, function(term) { - return utils.some(item.tokens, function(token) { - return token.indexOf(term) === 0; - }); - }); - isMatch && suggestions.push(item); - }); - return suggestions; - }, - initialize: function() { - var deferred; - this.local && this._processLocalData(this.local); - this.transport = this.remote ? new Transport(this.remote) : null; - deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); - this.local = this.prefetch = this.remote = null; - this.initialize = function() { - return deferred; - }; - return deferred; - }, - getSuggestions: function(query, cb) { - var that = this, terms, suggestions, cacheHit = false; - if (query.length < this.minLength) { - return; - } - terms = utils.tokenizeQuery(query); - suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); - if (suggestions.length < this.limit && this.transport) { - cacheHit = this.transport.get(query, processRemoteData); - } - !cacheHit && cb && cb(suggestions); - function processRemoteData(data) { - suggestions = suggestions.slice(0); - utils.each(data, function(i, datum) { - var item = that._transformDatum(datum), isDuplicate; - isDuplicate = utils.some(suggestions, function(suggestion) { - //return item.value === suggestion.value; - return false; - }); - !isDuplicate && suggestions.push(item); - return suggestions.length < that.limit; - }); - cb && cb(suggestions); - } + function onAsync(types, cb, context) { + return on.call(this, "async", types, cb, context); + } + function onSync(types, cb, context) { + return on.call(this, "sync", types, cb, context); + } + function off(types) { + var type; + if (!this._callbacks) { + return this; } - }); - return Dataset; - function compileTemplate(template, engine, valueKey) { - var renderFn, compiledTemplate; - if (utils.isFunction(template)) { - renderFn = template; - } else if (utils.isString(template)) { - compiledTemplate = engine.compile(template); - renderFn = utils.bind(compiledTemplate.render, compiledTemplate); + types = types.split(splitter); + while (type = types.shift()) { + delete this._callbacks[type]; + } + return this; + } + function trigger(types) { + var type, callbacks, args, syncFlush, asyncFlush; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + args = [].slice.call(arguments, 1); + while ((type = types.shift()) && (callbacks = this._callbacks[type])) { + syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); + asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); + syncFlush() && nextTick(asyncFlush); + } + return this; + } + function getFlush(callbacks, context, args) { + return flush; + function flush() { + var cancelled; + for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { + cancelled = callbacks[i].apply(context, args) === false; + } + return !cancelled; + } + } + function getNextTick() { + var nextTickFn; + if (window.setImmediate) { + nextTickFn = function nextTickSetImmediate(fn) { + setImmediate(function() { + fn(); + }); + }; } else { - renderFn = function(context) { - return "

    " + context[valueKey] + "

    "; + nextTickFn = function nextTickSetTimeout(fn) { + setTimeout(function() { + fn(); + }, 0); }; } - return renderFn; + return nextTickFn; + } + function bindContext(fn, context) { + return fn.bind ? fn.bind(context) : function() { + fn.apply(context, [].slice.call(arguments, 0)); + }; } }(); - var InputView = function() { - function InputView(o) { - var that = this; - utils.bindAll(this); - this.specialKeyCodeMap = { - // START METAMAPS CODE - //9: "tab", - // END METAMAPS CODE - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - this.$hint = $(o.hint); - this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); - if (!utils.isMsie()) { - this.$input.on("input.tt", this._compareQueryToInputValue); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - utils.defer(that._compareQueryToInputValue); - }); + var highlight = function(doc) { + "use strict"; + var defaults = { + node: null, + pattern: null, + tagName: "strong", + className: null, + wordsOnly: false, + caseSensitive: false + }; + return function hightlight(o) { + var regex; + o = _.mixin({}, defaults, o); + if (!o.node || !o.pattern) { + return; } - this.query = this.$input.val(); - this.$overflowHelper = buildOverflowHelper(this.$input); + o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; + regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); + traverse(o.node, hightlightTextNode); + function hightlightTextNode(textNode) { + var match, patternNode, wrapperNode; + if (match = regex.exec(textNode.data)) { + wrapperNode = doc.createElement(o.tagName); + o.className && (wrapperNode.className = o.className); + patternNode = textNode.splitText(match.index); + patternNode.splitText(match[0].length); + wrapperNode.appendChild(patternNode.cloneNode(true)); + textNode.parentNode.replaceChild(wrapperNode, patternNode); + } + return !!match; + } + function traverse(el, hightlightTextNode) { + var childNode, TEXT_NODE_TYPE = 3; + for (var i = 0; i < el.childNodes.length; i++) { + childNode = el.childNodes[i]; + if (childNode.nodeType === TEXT_NODE_TYPE) { + i += hightlightTextNode(childNode) ? 1 : 0; + } else { + traverse(childNode, hightlightTextNode); + } + } + } + }; + function getRegex(patterns, caseSensitive, wordsOnly) { + var escapedPatterns = [], regexStr; + for (var i = 0, len = patterns.length; i < len; i++) { + escapedPatterns.push(_.escapeRegExChars(patterns[i])); + } + regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; + return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); } - utils.mixin(InputView.prototype, EventTarget, { - _handleFocus: function() { + }(window.document); + var Input = function() { + "use strict"; + var specialKeyCodeMap; + specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + function Input(o, www) { + o = o || {}; + if (!o.input) { + $.error("input is missing"); + } + www.mixin(this); + this.$hint = $(o.hint); + this.$input = $(o.input); + this.query = this.$input.val(); + this.queryWhenFocused = this.hasFocus() ? this.query : null; + this.$overflowHelper = buildOverflowHelper(this.$input); + this._checkLanguageDirection(); + if (this.$hint.length === 0) { + this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; + } + } + Input.normalizeQuery = function(str) { + return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + }; + _.mixin(Input.prototype, EventEmitter, { + _onBlur: function onBlur() { + this.resetInputValue(); + this.trigger("blurred"); + }, + _onFocus: function onFocus() { + this.queryWhenFocused = this.query; this.trigger("focused"); }, - _handleBlur: function() { - this.trigger("blured"); - }, - _handleSpecialKeyEvent: function($e) { - var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; - keyName && this.trigger(keyName + "Keyed", $e); - }, - _compareQueryToInputValue: function() { - var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; - if (isSameQueryExceptWhitespace) { - this.trigger("whitespaceChanged", { - value: this.query - }); - } else if (!isSameQuery) { - this.trigger("queryChanged", { - value: this.query = inputValue - }); + _onKeydown: function onKeydown($e) { + var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; + this._managePreventDefault(keyName, $e); + if (keyName && this._shouldTrigger(keyName, $e)) { + this.trigger(keyName + "Keyed", $e); } }, - destroy: function() { - this.$hint.off(".tt"); - this.$input.off(".tt"); - this.$hint = this.$input = this.$overflowHelper = null; + _onInput: function onInput() { + this._setQuery(this.getInputValue()); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); }, - focus: function() { + _managePreventDefault: function managePreventDefault(keyName, $e) { + var preventDefault; + switch (keyName) { + case "up": + case "down": + preventDefault = !withModifier($e); + break; + + default: + preventDefault = false; + } + preventDefault && $e.preventDefault(); + }, + _shouldTrigger: function shouldTrigger(keyName, $e) { + var trigger; + switch (keyName) { + case "tab": + trigger = !withModifier($e); + break; + + default: + trigger = true; + } + return trigger; + }, + _checkLanguageDirection: function checkLanguageDirection() { + var dir = (this.$input.css("direction") || "ltr").toLowerCase(); + if (this.dir !== dir) { + this.dir = dir; + this.$hint.attr("dir", dir); + this.trigger("langDirChanged", dir); + } + }, + _setQuery: function setQuery(val, silent) { + var areEquivalent, hasDifferentWhitespace; + areEquivalent = areQueriesEquivalent(val, this.query); + hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; + this.query = val; + if (!silent && !areEquivalent) { + this.trigger("queryChanged", this.query); + } else if (!silent && hasDifferentWhitespace) { + this.trigger("whitespaceChanged", this.query); + } + }, + bind: function() { + var that = this, onBlur, onFocus, onKeydown, onInput; + onBlur = _.bind(this._onBlur, this); + onFocus = _.bind(this._onFocus, this); + onKeydown = _.bind(this._onKeydown, this); + onInput = _.bind(this._onInput, this); + this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); + if (!_.isMsie() || _.isMsie() > 9) { + this.$input.on("input.tt", onInput); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + _.defer(_.bind(that._onInput, that, $e)); + }); + } + return this; + }, + focus: function focus() { this.$input.focus(); }, - blur: function() { + blur: function blur() { this.$input.blur(); }, - getQuery: function() { - return this.query; + getLangDir: function getLangDir() { + return this.dir; }, - setQuery: function(query) { - this.query = query; + getQuery: function getQuery() { + return this.query || ""; }, - getInputValue: function() { + setQuery: function setQuery(val, silent) { + this.setInputValue(val); + this._setQuery(val, silent); + }, + hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { + return this.query !== this.queryWhenFocused; + }, + getInputValue: function getInputValue() { return this.$input.val(); }, - setInputValue: function(value, silent) { + setInputValue: function setInputValue(value) { this.$input.val(value); - !silent && this._compareQueryToInputValue(); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); }, - getHintValue: function() { + resetInputValue: function resetInputValue() { + this.setInputValue(this.query); + }, + getHint: function getHint() { return this.$hint.val(); }, - setHintValue: function(value) { + setHint: function setHint(value) { this.$hint.val(value); }, - getLanguageDirection: function() { - return (this.$input.css("direction") || "ltr").toLowerCase(); + clearHint: function clearHint() { + this.setHint(""); }, - isOverflow: function() { + clearHintIfInvalid: function clearHintIfInvalid() { + var val, hint, valIsPrefixOfHint, isValid; + val = this.getInputValue(); + hint = this.getHint(); + valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; + isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); + !isValid && this.clearHint(); + }, + hasFocus: function hasFocus() { + return this.$input.is(":focus"); + }, + hasOverflow: function hasOverflow() { + var constraint = this.$input.width() - 2; this.$overflowHelper.text(this.getInputValue()); - return this.$overflowHelper.width() > this.$input.width(); + return this.$overflowHelper.width() >= constraint; }, isCursorAtEnd: function() { - var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; - if (utils.isNumber(selectionStart)) { + var valueLength, selectionStart, range; + valueLength = this.$input.val().length; + selectionStart = this.$input[0].selectionStart; + if (_.isNumber(selectionStart)) { return selectionStart === valueLength; } else if (document.selection) { range = document.selection.createRange(); @@ -661,15 +610,20 @@ return valueLength === range.text.length; } return true; + }, + destroy: function destroy() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$overflowHelper.remove(); + this.$hint = this.$input = this.$overflowHelper = $("
    "); } }); - return InputView; + return Input; function buildOverflowHelper($input) { - return $("").css({ + return $('').css({ position: "absolute", - left: "-9999px", visibility: "hidden", - whiteSpace: "nowrap", + whiteSpace: "pre", fontFamily: $input.css("font-family"), fontSize: $input.css("font-size"), fontStyle: $input.css("font-style"), @@ -682,484 +636,903 @@ textTransform: $input.css("text-transform") }).insertAfter($input); } - function compareQueries(a, b) { - a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - return a === b; + function areQueriesEquivalent(a, b) { + return Input.normalizeQuery(a) === Input.normalizeQuery(b); + } + function withModifier($e) { + return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; } }(); - var DropdownView = function() { - var html = { - suggestionsList: '' - }, css = { - suggestionsList: { - display: "block" - }, - suggestion: { - whiteSpace: "nowrap", - cursor: "pointer" - }, - suggestionChild: { - whiteSpace: "normal" - } + var Dataset = function() { + "use strict"; + var keys, nameGenerator; + keys = { + val: "tt-selectable-display", + obj: "tt-selectable-object" }; - function DropdownView(o) { - utils.bindAll(this); - this.isOpen = false; - this.isEmpty = true; - this.isMouseOverDropdown = false; - this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover); + nameGenerator = _.getIdGenerator(); + function Dataset(o, www) { + o = o || {}; + o.templates = o.templates || {}; + o.templates.notFound = o.templates.notFound || o.templates.empty; + if (!o.source) { + $.error("missing source"); + } + if (!o.node) { + $.error("missing node"); + } + if (o.name && !isValidName(o.name)) { + $.error("invalid dataset name: " + o.name); + } + www.mixin(this); + this.highlight = !!o.highlight; + this.name = o.name || nameGenerator(); + this.limit = o.limit || 5; + this.displayFn = getDisplayFn(o.display || o.displayKey); + this.templates = getTemplates(o.templates, this.displayFn); + this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; + this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; + this._resetLastSuggestion(); + this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); } - utils.mixin(DropdownView.prototype, EventTarget, { - _handleMouseenter: function() { - this.isMouseOverDropdown = true; - }, - _handleMouseleave: function() { - this.isMouseOverDropdown = false; - - // START METAMAPS CODE - this._getSuggestions().removeClass("tt-is-under-cursor"); - this._getSuggestions().removeClass("tt-is-under-mouse-cursor"); - // END METAMAPS CODE - }, - _handleMouseover: function($e) { - var $suggestion = $($e.currentTarget); - this._getSuggestions().removeClass("tt-is-under-cursor"); - // START METAMAPS CODE - this._getSuggestions().removeClass("tt-is-under-mouse-cursor"); - $suggestion.addClass("tt-is-under-mouse-cursor"); - // ORIGINAL CODE $suggestion.addClass("tt-is-under-cursor"); - }, - _handleSelection: function($e) { - var $suggestion = $($e.currentTarget); - this.trigger("suggestionSelected", extractSuggestion($suggestion)); - }, - _show: function() { - this.$menu.css("display", "block"); - }, - _hide: function() { - this.$menu.hide(); - }, - _moveCursor: function(increment) { - var $suggestions, $cur, nextIndex, $underCursor; - if (!this.isVisible()) { - return; - } - $suggestions = this._getSuggestions(); - $cur = $suggestions.filter(".tt-is-under-cursor"); - $cur.removeClass("tt-is-under-cursor"); - nextIndex = $suggestions.index($cur) + increment; - nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; - if (nextIndex === -1) { - this.trigger("cursorRemoved"); - return; - } else if (nextIndex < -1) { - nextIndex = $suggestions.length - 1; - } - $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); - this._ensureVisibility($underCursor); - this.trigger("cursorMoved", extractSuggestion($underCursor)); - }, - _getSuggestions: function() { - return this.$menu.find(".tt-suggestions > .tt-suggestion"); - }, - _ensureVisibility: function($el) { - var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true); - if (elTop < 0) { - this.$menu.scrollTop(menuScrollTop + elTop); - } else if (menuHeight < elBottom) { - this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); - } - }, - destroy: function() { - this.$menu.off(".tt"); - this.$menu = null; - }, - isVisible: function() { - return this.isOpen && !this.isEmpty; - }, - closeUnlessMouseIsOverDropdown: function() { - if (!this.isMouseOverDropdown) { - this.close(); - } - }, - close: function() { - if (this.isOpen) { - this.isOpen = false; - this.isMouseOverDropdown = false; - this._hide(); - this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); - this.trigger("closed"); - } - }, - open: function() { - if (!this.isOpen) { - this.isOpen = true; - !this.isEmpty && this._show(); - this.trigger("opened"); - } - }, - setLanguageDirection: function(dir) { - var ltrCss = { - left: "0", - right: "auto" - }, rtlCss = { - left: "auto", - right: " 0" + Dataset.extractData = function extractData(el) { + var $el = $(el); + if ($el.data(keys.obj)) { + return { + val: $el.data(keys.val) || "", + obj: $el.data(keys.obj) || null }; - dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); - }, - moveCursorUp: function() { - this._moveCursor(-1); - }, - moveCursorDown: function() { - this._moveCursor(+1); - }, - getSuggestionUnderCursor: function() { - var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); - return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; - }, - getFirstSuggestion: function() { - var $suggestion = this._getSuggestions().first(); - return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; - }, - renderSuggestions: function(dataset, suggestions) { - var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '
    %body
    ', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el; - if ($dataset.length === 0) { - $suggestionsList = $(html.suggestionsList).css(css.suggestionsList); - $dataset = $("
    ").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu); - } - if (suggestions.length > 0) { - this.isEmpty = false; - this.isOpen && this._show(); - elBuilder = document.createElement("div"); - fragment = document.createDocumentFragment(); - utils.each(suggestions, function(i, suggestion) { - suggestion.dataset = dataset.name; - compiledHtml = dataset.template(suggestion.datum); - elBuilder.innerHTML = wrapper.replace("%body", compiledHtml); - $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion); - $el.children().each(function() { - $(this).css(css.suggestionChild); - }); - fragment.appendChild($el[0]); - }); - $dataset.show().find(".tt-suggestions").html(fragment); - } else { - this.clearSuggestions(dataset.name); - } - this.trigger("suggestionsRendered"); - }, - clearSuggestions: function(datasetName) { - var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions"); - $datasets.hide(); - $suggestions.empty(); - if (this._getSuggestions().length === 0) { - this.isEmpty = true; - this._hide(); - } - } - }); - return DropdownView; - function extractSuggestion($el) { - return $el.data("suggestion"); - } - }(); - var TypeaheadView = function() { - var html = { - wrapper: '', - hint: '', - dropdown: '' - }, css = { - wrapper: { - position: "relative", - display: "inline-block" - }, - hint: { - position: "absolute", - top: "0", - left: "0", - borderColor: "transparent", - boxShadow: "none" - }, - query: { - position: "relative", - verticalAlign: "top", - backgroundColor: "transparent" - }, - dropdown: { - position: "absolute", - top: "100%", - left: "0", - zIndex: "100", - display: "none" } + return null; }; - if (utils.isMsie()) { - utils.mixin(css.query, { - backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" - }); - } - if (utils.isMsie() && utils.isMsie() <= 7) { - utils.mixin(css.wrapper, { - display: "inline", - zoom: "1" - }); - utils.mixin(css.query, { - marginTop: "-1px" - }); - } - function TypeaheadView(o) { - var $menu, $input, $hint; - utils.bindAll(this); - this.$node = buildDomStructure(o.input); - this.datasets = o.datasets; - this.dir = null; - this.eventBus = o.eventBus; - $menu = this.$node.find(".tt-dropdown-menu"); - $input = this.$node.find(".tt-query"); - $hint = this.$node.find(".tt-hint"); - this.dropdownView = new DropdownView({ - menu: $menu - }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent); - // START METAMAPS CODE - this.dropdownView.on('suggestionsRendered', this._suggestionsRendered); - // END METAMAPS CODE - - this.inputView = new InputView({ - input: $input, - hint: $hint - }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete); - // START METAMAPS CODE - this.inputView.on('queryChanged', this._queryChanged); - // END METAMAPS CODE - } - utils.mixin(TypeaheadView.prototype, EventTarget, { - _managePreventDefault: function(e) { - var $e = e.data, hint, inputValue, preventDefault = false; - switch (e.type) { - case "tabKeyed": - hint = this.inputView.getHintValue(); - inputValue = this.inputView.getInputValue(); - preventDefault = hint && hint !== inputValue; - break; - - case "upKeyed": - case "downKeyed": - preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey; - break; + _.mixin(Dataset.prototype, EventEmitter, { + _overwrite: function overwrite(query, suggestions) { + suggestions = suggestions || []; + if (suggestions.length) { + this._renderSuggestions(query, suggestions); + } else if (this.async && this.templates.pending) { + this._renderPending(query); + } else if (!this.async && this.templates.notFound) { + this._renderNotFound(query); + } else { + this._empty(); } - preventDefault && $e.preventDefault(); + this.trigger("rendered", this.name, suggestions, false); }, - _setLanguageDirection: function() { - var dir = this.inputView.getLanguageDirection(); - if (dir !== this.dir) { - this.dir = dir; - this.$node.css("direction", dir); - this.dropdownView.setLanguageDirection(dir); + _append: function append(query, suggestions) { + suggestions = suggestions || []; + if (suggestions.length && this.$lastSuggestion.length) { + this._appendSuggestions(query, suggestions); + } else if (suggestions.length) { + this._renderSuggestions(query, suggestions); + } else if (!this.$lastSuggestion.length && this.templates.notFound) { + this._renderNotFound(query); } + this.trigger("rendered", this.name, suggestions, true); }, - // START METAMAPS CODE - _suggestionsRendered: function() { - this.eventBus.trigger('suggestionsRendered'); + _renderSuggestions: function renderSuggestions(query, suggestions) { + var $fragment; + $fragment = this._getSuggestionsFragment(query, suggestions); + this.$lastSuggestion = $fragment.children().last(); + this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); }, - _queryChanged: function() { - this.eventBus.trigger('queryChanged'); + _appendSuggestions: function appendSuggestions(query, suggestions) { + var $fragment, $lastSuggestion; + $fragment = this._getSuggestionsFragment(query, suggestions); + $lastSuggestion = $fragment.children().last(); + this.$lastSuggestion.after($fragment); + this.$lastSuggestion = $lastSuggestion; }, - // END METAMAPS CODE - _updateHint: function() { - var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match; - if (hint && dropdownIsVisible && !inputHasOverflow) { - inputValue = this.inputView.getInputValue(); - query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, ""); - escapedQuery = utils.escapeRegExChars(query); - beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); - match = beginsWithQuery.exec(hint); - this.inputView.setHintValue(inputValue + (match ? match[1] : "")); - } + _renderPending: function renderPending(query) { + var template = this.templates.pending; + this._resetLastSuggestion(); + template && this.$el.html(template({ + query: query, + dataset: this.name + })); }, - _clearHint: function() { - this.inputView.setHintValue(""); + _renderNotFound: function renderNotFound(query) { + var template = this.templates.notFound; + this._resetLastSuggestion(); + template && this.$el.html(template({ + query: query, + dataset: this.name + })); }, - _clearSuggestions: function() { - this.dropdownView.clearSuggestions(); + _empty: function empty() { + this.$el.empty(); + this._resetLastSuggestion(); }, - _setInputValueToQuery: function() { - this.inputView.setInputValue(this.inputView.getQuery()); - }, - _setInputValueToSuggestionUnderCursor: function(e) { - var suggestion = e.data; - this.inputView.setInputValue(suggestion.value, true); - }, - _openDropdown: function() { - this.dropdownView.open(); - }, - _closeDropdown: function(e) { - this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"](); - }, - _moveDropdownCursor: function(e) { - var $e = e.data; - if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) { - this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"](); - } - }, - _handleSelection: function(e) { - var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor(); - if (suggestion) { - this.inputView.setInputValue(suggestion.value); - byClick ? this.inputView.focus() : e.data.preventDefault(); - byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close(); - this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset); - } - }, - _getSuggestions: function() { - var that = this, query = this.inputView.getQuery(); - if (utils.isBlankString(query)) { - return; - } - utils.each(this.datasets, function(i, dataset) { - dataset.getSuggestions(query, function(suggestions) { - if (query === that.inputView.getQuery()) { - that.dropdownView.renderSuggestions(dataset, suggestions); - } - }); + _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { + var that = this, fragment; + fragment = document.createDocumentFragment(); + _.each(suggestions, function getSuggestionNode(suggestion) { + var $el, context; + context = that._injectQuery(query, suggestion); + $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); + fragment.appendChild($el[0]); }); + this.highlight && highlight({ + className: this.classes.highlight, + node: fragment, + pattern: query + }); + return $(fragment); }, - _autocomplete: function(e) { - var isCursorAtEnd, ignoreEvent, query, hint, suggestion; - if (e.type === "rightKeyed" || e.type === "leftKeyed") { - isCursorAtEnd = this.inputView.isCursorAtEnd(); - ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed"; - if (!isCursorAtEnd || ignoreEvent) { + _getFooter: function getFooter(query, suggestions) { + return this.templates.footer ? this.templates.footer({ + query: query, + suggestions: suggestions, + dataset: this.name + }) : null; + }, + _getHeader: function getHeader(query, suggestions) { + return this.templates.header ? this.templates.header({ + query: query, + suggestions: suggestions, + dataset: this.name + }) : null; + }, + _resetLastSuggestion: function resetLastSuggestion() { + this.$lastSuggestion = $(); + }, + _injectQuery: function injectQuery(query, obj) { + return _.isObject(obj) ? _.mixin({ + _query: query + }, obj) : obj; + }, + update: function update(query) { + var that = this, canceled = false, syncCalled = false, rendered = 0; + this.cancel(); + this.cancel = function cancel() { + canceled = true; + that.cancel = $.noop; + that.async && that.trigger("asyncCanceled", query); + }; + this.source(query, sync, async); + !syncCalled && sync([]); + function sync(suggestions) { + if (syncCalled) { return; } + syncCalled = true; + suggestions = (suggestions || []).slice(0, that.limit); + rendered = suggestions.length; + that._overwrite(query, suggestions); + if (rendered < that.limit && that.async) { + that.trigger("asyncRequested", query); + } } - query = this.inputView.getQuery(); - hint = this.inputView.getHintValue(); - if (hint !== "" && query !== hint) { - suggestion = this.dropdownView.getFirstSuggestion(); - this.inputView.setInputValue(suggestion.value); - this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset); + function async(suggestions) { + suggestions = suggestions || []; + if (!canceled && rendered < that.limit) { + that.cancel = $.noop; + rendered += suggestions.length; + that._append(query, suggestions.slice(0, that.limit - rendered)); + that.async && that.trigger("asyncReceived", query); + } } }, - _propagateEvent: function(e) { - this.eventBus.trigger(e.type); + cancel: $.noop, + clear: function clear() { + this._empty(); + this.cancel(); + this.trigger("cleared"); }, - destroy: function() { - this.inputView.destroy(); - this.dropdownView.destroy(); - destroyDomStructure(this.$node); - this.$node = null; + isEmpty: function isEmpty() { + return this.$el.is(":empty"); }, - setQuery: function(query) { - this.inputView.setQuery(query); - this.inputView.setInputValue(query); - this._clearHint(); - this._clearSuggestions(); - this._getSuggestions(); + destroy: function destroy() { + this.$el = $("
    "); } }); - return TypeaheadView; - function buildDomStructure(input) { - var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint); - $wrapper = $wrapper.css(css.wrapper); - $dropdown = $dropdown.css(css.dropdown); - $hint.css(css.hint).css({ - backgroundAttachment: $input.css("background-attachment"), - backgroundClip: $input.css("background-clip"), - backgroundColor: $input.css("background-color"), - backgroundImage: $input.css("background-image"), - backgroundOrigin: $input.css("background-origin"), - backgroundPosition: $input.css("background-position"), - backgroundRepeat: $input.css("background-repeat"), - backgroundSize: $input.css("background-size") - }); - $input.data("ttAttrs", { - dir: $input.attr("dir"), - autocomplete: $input.attr("autocomplete"), - spellcheck: $input.attr("spellcheck"), - style: $input.attr("style") - }); - $input.addClass("tt-query").attr({ - autocomplete: "off", - spellcheck: false - }).css(css.query); - try { - !$input.attr("dir") && $input.attr("dir", "auto"); - } catch (e) {} - return $input.wrap($wrapper).parent().prepend($hint).append($dropdown); + return Dataset; + function getDisplayFn(display) { + display = display || _.stringify; + return _.isFunction(display) ? display : displayFn; + function displayFn(obj) { + return obj[display]; + } } - function destroyDomStructure($node) { - var $input = $node.find(".tt-query"); - utils.each($input.data("ttAttrs"), function(key, val) { - utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); - }); - $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node); - $node.remove(); + function getTemplates(templates, displayFn) { + return { + notFound: templates.notFound && _.templatify(templates.notFound), + pending: templates.pending && _.templatify(templates.pending), + header: templates.header && _.templatify(templates.header), + footer: templates.footer && _.templatify(templates.footer), + suggestion: templates.suggestion || suggestionTemplate + }; + function suggestionTemplate(context) { + return $("
    ").text(displayFn(context)); + } + } + function isValidName(str) { + return /^[_a-zA-Z0-9-]+$/.test(str); + } + }(); + var Menu = function() { + "use strict"; + function Menu(o, www) { + var that = this; + o = o || {}; + if (!o.node) { + $.error("node is required"); + } + www.mixin(this); + this.$node = $(o.node); + this.query = null; + this.datasets = _.map(o.datasets, initializeDataset); + function initializeDataset(oDataset) { + var node = that.$node.find(oDataset.node).first(); + oDataset.node = node.length ? node : $("
    ").appendTo(that.$node); + return new Dataset(oDataset, www); + } + } + _.mixin(Menu.prototype, EventEmitter, { + _onSelectableClick: function onSelectableClick($e) { + this.trigger("selectableClicked", $($e.currentTarget)); + }, + _onRendered: function onRendered(type, dataset, suggestions, async) { + this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); + this.trigger("datasetRendered", dataset, suggestions, async); + }, + _onCleared: function onCleared() { + this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); + this.trigger("datasetCleared"); + }, + _propagate: function propagate() { + this.trigger.apply(this, arguments); + }, + _allDatasetsEmpty: function allDatasetsEmpty() { + return _.every(this.datasets, isDatasetEmpty); + function isDatasetEmpty(dataset) { + return dataset.isEmpty(); + } + }, + _getSelectables: function getSelectables() { + return this.$node.find(this.selectors.selectable); + }, + _removeCursor: function _removeCursor() { + var $selectable = this.getActiveSelectable(); + $selectable && $selectable.removeClass(this.classes.cursor); + }, + _ensureVisible: function ensureVisible($el) { + var elTop, elBottom, nodeScrollTop, nodeHeight; + elTop = $el.position().top; + elBottom = elTop + $el.outerHeight(true); + nodeScrollTop = this.$node.scrollTop(); + nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); + if (elTop < 0) { + this.$node.scrollTop(nodeScrollTop + elTop); + } else if (nodeHeight < elBottom) { + this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); + } + }, + bind: function() { + var that = this, onSelectableClick; + onSelectableClick = _.bind(this._onSelectableClick, this); + this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); + _.each(this.datasets, function(dataset) { + dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); + }); + return this; + }, + isOpen: function isOpen() { + return this.$node.hasClass(this.classes.open); + }, + open: function open() { + this.$node.addClass(this.classes.open); + }, + close: function close() { + this.$node.removeClass(this.classes.open); + this._removeCursor(); + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$node.attr("dir", dir); + }, + selectableRelativeToCursor: function selectableRelativeToCursor(delta) { + var $selectables, $oldCursor, oldIndex, newIndex; + $oldCursor = this.getActiveSelectable(); + $selectables = this._getSelectables(); + oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; + newIndex = oldIndex + delta; + newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; + newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; + return newIndex === -1 ? null : $selectables.eq(newIndex); + }, + setCursor: function setCursor($selectable) { + this._removeCursor(); + if ($selectable = $selectable && $selectable.first()) { + $selectable.addClass(this.classes.cursor); + this._ensureVisible($selectable); + } + }, + getSelectableData: function getSelectableData($el) { + return $el && $el.length ? Dataset.extractData($el) : null; + }, + getActiveSelectable: function getActiveSelectable() { + var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); + return $selectable.length ? $selectable : null; + }, + getTopSelectable: function getTopSelectable() { + var $selectable = this._getSelectables().first(); + return $selectable.length ? $selectable : null; + }, + update: function update(query) { + var isValidUpdate = query !== this.query; + if (isValidUpdate) { + this.query = query; + _.each(this.datasets, updateDataset); + } + return isValidUpdate; + function updateDataset(dataset) { + dataset.update(query); + } + }, + empty: function empty() { + _.each(this.datasets, clearDataset); + this.query = null; + this.$node.addClass(this.classes.empty); + function clearDataset(dataset) { + dataset.clear(); + } + }, + destroy: function destroy() { + this.$node.off(".tt"); + this.$node = $("
    "); + _.each(this.datasets, destroyDataset); + function destroyDataset(dataset) { + dataset.destroy(); + } + } + }); + return Menu; + }(); + var DefaultMenu = function() { + "use strict"; + var s = Menu.prototype; + function DefaultMenu() { + Menu.apply(this, [].slice.call(arguments, 0)); + } + _.mixin(DefaultMenu.prototype, Menu.prototype, { + open: function open() { + !this._allDatasetsEmpty() && this._show(); + return s.open.apply(this, [].slice.call(arguments, 0)); + }, + close: function close() { + this._hide(); + return s.close.apply(this, [].slice.call(arguments, 0)); + }, + _onRendered: function onRendered() { + if (this._allDatasetsEmpty()) { + this._hide(); + } else { + this.isOpen() && this._show(); + } + return s._onRendered.apply(this, [].slice.call(arguments, 0)); + }, + _onCleared: function onCleared() { + if (this._allDatasetsEmpty()) { + this._hide(); + } else { + this.isOpen() && this._show(); + } + return s._onCleared.apply(this, [].slice.call(arguments, 0)); + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); + return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); + }, + _hide: function hide() { + this.$node.hide(); + }, + _show: function show() { + this.$node.css("display", "block"); + } + }); + return DefaultMenu; + }(); + var Typeahead = function() { + "use strict"; + function Typeahead(o, www) { + var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; + o = o || {}; + if (!o.input) { + $.error("missing input"); + } + if (!o.menu) { + $.error("missing menu"); + } + if (!o.eventBus) { + $.error("missing event bus"); + } + www.mixin(this); + this.eventBus = o.eventBus; + this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; + this.input = o.input; + this.menu = o.menu; + this.enabled = true; + this.active = false; + this.input.hasFocus() && this.activate(); + this.dir = this.input.getLangDir(); + this._hacks(); + this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); + onFocused = c(this, "activate", "open", "_onFocused"); + onBlurred = c(this, "deactivate", "_onBlurred"); + onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); + onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); + onEscKeyed = c(this, "isActive", "_onEscKeyed"); + onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); + onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); + onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); + onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); + onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); + onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); + this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); + } + _.mixin(Typeahead.prototype, { + _hacks: function hacks() { + var $input, $menu; + $input = this.input.$input || $("
    "); + $menu = this.menu.$node || $("
    "); + $input.on("blur.tt", function($e) { + var active, isActive, hasActive; + active = document.activeElement; + isActive = $menu.is(active); + hasActive = $menu.has(active).length > 0; + if (_.isMsie() && (isActive || hasActive)) { + $e.preventDefault(); + $e.stopImmediatePropagation(); + _.defer(function() { + $input.focus(); + }); + } + }); + $menu.on("mousedown.tt", function($e) { + $e.preventDefault(); + }); + }, + _onSelectableClicked: function onSelectableClicked(type, $el) { + this.select($el); + }, + _onDatasetCleared: function onDatasetCleared() { + this._updateHint(); + }, + _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) { + this._updateHint(); + this.eventBus.trigger("render", suggestions, async, dataset); + }, + _onAsyncRequested: function onAsyncRequested(type, dataset, query) { + this.eventBus.trigger("asyncrequest", query, dataset); + }, + _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { + this.eventBus.trigger("asynccancel", query, dataset); + }, + _onAsyncReceived: function onAsyncReceived(type, dataset, query) { + this.eventBus.trigger("asyncreceive", query, dataset); + }, + _onFocused: function onFocused() { + this._minLengthMet() && this.menu.update(this.input.getQuery()); + }, + _onBlurred: function onBlurred() { + if (this.input.hasQueryChangedSinceLastFocus()) { + this.eventBus.trigger("change", this.input.getQuery()); + } + }, + _onEnterKeyed: function onEnterKeyed(type, $e) { + var $selectable; + if ($selectable = this.menu.getActiveSelectable()) { + this.select($selectable) && $e.preventDefault(); + } + }, + _onTabKeyed: function onTabKeyed(type, $e) { + var $selectable; + if ($selectable = this.menu.getActiveSelectable()) { + this.select($selectable) && $e.preventDefault(); + } else if ($selectable = this.menu.getTopSelectable()) { + this.autocomplete($selectable) && $e.preventDefault(); + } + }, + _onEscKeyed: function onEscKeyed() { + this.close(); + }, + _onUpKeyed: function onUpKeyed() { + this.moveCursor(-1); + }, + _onDownKeyed: function onDownKeyed() { + this.moveCursor(+1); + }, + _onLeftKeyed: function onLeftKeyed() { + if (this.dir === "rtl" && this.input.isCursorAtEnd()) { + this.autocomplete(this.menu.getTopSelectable()); + } + }, + _onRightKeyed: function onRightKeyed() { + if (this.dir === "ltr" && this.input.isCursorAtEnd()) { + this.autocomplete(this.menu.getTopSelectable()); + } + }, + _onQueryChanged: function onQueryChanged(e, query) { + this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); + }, + _onWhitespaceChanged: function onWhitespaceChanged() { + this._updateHint(); + }, + _onLangDirChanged: function onLangDirChanged(e, dir) { + if (this.dir !== dir) { + this.dir = dir; + this.menu.setLanguageDirection(dir); + } + }, + _openIfActive: function openIfActive() { + this.isActive() && this.open(); + }, + _minLengthMet: function minLengthMet(query) { + query = _.isString(query) ? query : this.input.getQuery() || ""; + return query.length >= this.minLength; + }, + _updateHint: function updateHint() { + var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; + $selectable = this.menu.getTopSelectable(); + data = this.menu.getSelectableData($selectable); + val = this.input.getInputValue(); + if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { + query = Input.normalizeQuery(val); + escapedQuery = _.escapeRegExChars(query); + frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); + match = frontMatchRegEx.exec(data.val); + match && this.input.setHint(val + match[1]); + } else { + this.input.clearHint(); + } + }, + isEnabled: function isEnabled() { + return this.enabled; + }, + enable: function enable() { + this.enabled = true; + }, + disable: function disable() { + this.enabled = false; + }, + isActive: function isActive() { + return this.active; + }, + activate: function activate() { + if (this.isActive()) { + return true; + } else if (!this.isEnabled() || this.eventBus.before("active")) { + return false; + } else { + this.active = true; + this.eventBus.trigger("active"); + return true; + } + }, + deactivate: function deactivate() { + if (!this.isActive()) { + return true; + } else if (this.eventBus.before("idle")) { + return false; + } else { + this.active = false; + this.close(); + this.eventBus.trigger("idle"); + return true; + } + }, + isOpen: function isOpen() { + return this.menu.isOpen(); + }, + open: function open() { + if (!this.isOpen() && !this.eventBus.before("open")) { + this.menu.open(); + this._updateHint(); + this.eventBus.trigger("open"); + } + return this.isOpen(); + }, + close: function close() { + if (this.isOpen() && !this.eventBus.before("close")) { + this.menu.close(); + this.input.clearHint(); + this.input.resetInputValue(); + this.eventBus.trigger("close"); + } + return !this.isOpen(); + }, + setVal: function setVal(val) { + this.input.setQuery(_.toStr(val)); + }, + getVal: function getVal() { + return this.input.getQuery(); + }, + select: function select($selectable) { + var data = this.menu.getSelectableData($selectable); + if (data && !this.eventBus.before("select", data.obj)) { + this.input.setQuery(data.val, true); + this.eventBus.trigger("select", data.obj); + this.close(); + return true; + } + return false; + }, + autocomplete: function autocomplete($selectable) { + var query, data, isValid; + query = this.input.getQuery(); + data = this.menu.getSelectableData($selectable); + isValid = data && query !== data.val; + if (isValid && !this.eventBus.before("autocomplete", data.obj)) { + this.input.setQuery(data.val); + this.eventBus.trigger("autocomplete", data.obj); + return true; + } + return false; + }, + moveCursor: function moveCursor(delta) { + var query, $candidate, data, payload, cancelMove; + query = this.input.getQuery(); + $candidate = this.menu.selectableRelativeToCursor(delta); + data = this.menu.getSelectableData($candidate); + payload = data ? data.obj : null; + cancelMove = this._minLengthMet() && this.menu.update(query); + if (!cancelMove && !this.eventBus.before("cursorchange", payload)) { + this.menu.setCursor($candidate); + if (data) { + this.input.setInputValue(data.val); + } else { + this.input.resetInputValue(); + this._updateHint(); + } + this.eventBus.trigger("cursorchange", payload); + return true; + } + return false; + }, + destroy: function destroy() { + this.input.destroy(); + this.menu.destroy(); + } + }); + return Typeahead; + function c(ctx) { + var methods = [].slice.call(arguments, 1); + return function() { + var args = [].slice.call(arguments); + _.each(methods, function(method) { + return ctx[method].apply(ctx, args); + }); + }; } }(); (function() { - var cache = {}, viewKey = "ttView", methods; + "use strict"; + var old, keys, methods; + old = $.fn.typeahead; + keys = { + www: "tt-www", + attrs: "tt-attrs", + typeahead: "tt-typeahead" + }; methods = { - initialize: function(datasetDefs) { - var datasets; - datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ]; - if (datasetDefs.length === 0) { - $.error("no datasets provided"); - } - datasets = utils.map(datasetDefs, function(o) { - var dataset = cache[o.name] ? cache[o.name] : new Dataset(o); - if (o.name) { - cache[o.name] = dataset; + initialize: function initialize(o, datasets) { + var www; + datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); + o = o || {}; + www = WWW(o.classNames); + return this.each(attach); + function attach() { + var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor; + _.each(datasets, function(d) { + d.highlight = !!o.highlight; + }); + $input = $(this); + $wrapper = $(www.html.wrapper); + $hint = $elOrNull(o.hint); + $menu = $elOrNull(o.menu); + defaultHint = o.hint !== false && !$hint; + defaultMenu = o.menu !== false && !$menu; + defaultHint && ($hint = buildHintFromInput($input, www)); + defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); + $hint && $hint.val(""); + $input = prepInput($input, www); + if (defaultHint || defaultMenu) { + $wrapper.css(www.css.wrapper); + $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); + $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); } - return dataset; - }); - return this.each(initialize); - function initialize() { - var $input = $(this), deferreds, eventBus = new EventBus({ + MenuConstructor = defaultMenu ? DefaultMenu : Menu; + eventBus = new EventBus({ el: $input }); - deferreds = utils.map(datasets, function(dataset) { - return dataset.initialize(); - }); - $input.data(viewKey, new TypeaheadView({ - input: $input, - eventBus: eventBus = new EventBus({ - el: $input - }), + input = new Input({ + hint: $hint, + input: $input + }, www); + menu = new MenuConstructor({ + node: $menu, datasets: datasets - })); - $.when.apply($, deferreds).always(function() { - utils.defer(function() { - eventBus.trigger("initialized"); - }); + }, www); + typeahead = new Typeahead({ + input: input, + menu: menu, + eventBus: eventBus, + minLength: o.minLength + }, www); + $input.data(keys.www, www); + $input.data(keys.typeahead, typeahead); + } + }, + isEnabled: function isEnabled() { + var enabled; + ttEach(this.first(), function(t) { + enabled = t.isEnabled(); + }); + return enabled; + }, + enable: function enable() { + ttEach(this, function(t) { + t.enable(); + }); + return this; + }, + disable: function disable() { + ttEach(this, function(t) { + t.disable(); + }); + return this; + }, + isActive: function isActive() { + var active; + ttEach(this.first(), function(t) { + active = t.isActive(); + }); + return active; + }, + activate: function activate() { + ttEach(this, function(t) { + t.activate(); + }); + return this; + }, + deactivate: function deactivate() { + ttEach(this, function(t) { + t.deactivate(); + }); + return this; + }, + isOpen: function isOpen() { + var open; + ttEach(this.first(), function(t) { + open = t.isOpen(); + }); + return open; + }, + open: function open() { + ttEach(this, function(t) { + t.open(); + }); + return this; + }, + close: function close() { + ttEach(this, function(t) { + t.close(); + }); + return this; + }, + select: function select(el) { + var success = false, $el = $(el); + ttEach(this.first(), function(t) { + success = t.select($el); + }); + return success; + }, + autocomplete: function autocomplete(el) { + var success = false, $el = $(el); + ttEach(this.first(), function(t) { + success = t.autocomplete($el); + }); + return success; + }, + moveCursor: function moveCursoe(delta) { + var success = false; + ttEach(this.first(), function(t) { + success = t.moveCursor(delta); + }); + return success; + }, + val: function val(newVal) { + var query; + if (!arguments.length) { + ttEach(this.first(), function(t) { + query = t.getVal(); }); + return query; + } else { + ttEach(this, function(t) { + t.setVal(newVal); + }); + return this; } }, - destroy: function() { - return this.each(destroy); - function destroy() { - var $this = $(this), view = $this.data(viewKey); - if (view) { - view.destroy(); - $this.removeData(viewKey); - } - } - }, - setQuery: function(query) { - return this.each(setQuery); - function setQuery() { - var view = $(this).data(viewKey); - view && view.setQuery(query); - } + destroy: function destroy() { + ttEach(this, function(typeahead, $input) { + revert($input); + typeahead.destroy(); + }); + return this; } }; - jQuery.fn.typeahead = function(method) { + $.fn.typeahead = function(method) { if (methods[method]) { return methods[method].apply(this, [].slice.call(arguments, 1)); } else { return methods.initialize.apply(this, arguments); } }; + $.fn.typeahead.noConflict = function noConflict() { + $.fn.typeahead = old; + return this; + }; + function ttEach($els, fn) { + $els.each(function() { + var $input = $(this), typeahead; + (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); + }); + } + function buildHintFromInput($input, www) { + return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({ + autocomplete: "off", + spellcheck: "false", + tabindex: -1 + }); + } + function prepInput($input, www) { + $input.data(keys.attrs, { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass(www.classes.input).attr({ + autocomplete: "off", + spellcheck: false + }); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input; + } + function getBackgroundStyles($el) { + return { + backgroundAttachment: $el.css("background-attachment"), + backgroundClip: $el.css("background-clip"), + backgroundColor: $el.css("background-color"), + backgroundImage: $el.css("background-image"), + backgroundOrigin: $el.css("background-origin"), + backgroundPosition: $el.css("background-position"), + backgroundRepeat: $el.css("background-repeat"), + backgroundSize: $el.css("background-size") + }; + } + function revert($input) { + var www, $wrapper; + www = $input.data(keys.www); + $wrapper = $input.parent().filter(www.selectors.wrapper); + _.each($input.data(keys.attrs), function(val, key) { + _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); + if ($wrapper.length) { + $input.detach().insertAfter($wrapper); + $wrapper.remove(); + } + } + function $elOrNull(obj) { + var isValid, $el; + isValid = _.isJQuery(obj) || _.isElement(obj); + $el = isValid ? $(obj).first() : []; + return $el.length ? $el : null; + } })(); -})(window.jQuery); +}); \ No newline at end of file diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index af0518d0..5792eb96 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -669,7 +669,7 @@ Metamaps.Create = { { minLength: 2, }, - [{ + { name: 'topic_autocomplete', limit: 8, template: $('#topicAutocompleteTemplate').html(), @@ -677,7 +677,7 @@ Metamaps.Create = { url: '/topics/autocomplete_topic?term=%QUERY' }, engine: Hogan - }] + } ); // tell the autocomplete to submit the form with the topic you clicked on if you pick from the autocomplete @@ -731,7 +731,7 @@ Metamaps.Create = { { minLength: 2, }, - [{ + { name: 'synapse_autocomplete', template: "
    {{label}}
    ", remote: { @@ -751,7 +751,7 @@ Metamaps.Create = { }, engine: Hogan, header: "

    Existing synapses

    " - }] + } ); $('#synapse_desc').bind('typeahead:selected', function (event, datum, dataset) { From b3b065e6233c6275a0edc0d9115ee6c5a7263708 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 1 Oct 2015 11:34:19 +0800 Subject: [PATCH 027/122] Revert "upgrade typeahead to allow new syntax" This reverts commit 347d77df824362b14770eede6cc3ab21ee4d6f07. --- app/assets/javascripts/lib/typeahead.js | 2453 ++++++++++------------- app/assets/javascripts/src/Metamaps.js | 8 +- 2 files changed, 1044 insertions(+), 1417 deletions(-) diff --git a/app/assets/javascripts/lib/typeahead.js b/app/assets/javascripts/lib/typeahead.js index 2b089289..3a413d68 100644 --- a/app/assets/javascripts/lib/typeahead.js +++ b/app/assets/javascripts/lib/typeahead.js @@ -1,608 +1,659 @@ /*! - * typeahead.js 0.11.1 - * https://github.com/twitter/typeahead.js - * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT + * typeahead.js 0.9.3 + * https://github.com/twitter/typeahead + * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT */ -(function(root, factory) { - if (typeof define === "function" && define.amd) { - define("typeahead.js", [ "jquery" ], function(a0) { - return factory(a0); - }); - } else if (typeof exports === "object") { - module.exports = factory(require("jquery")); - } else { - factory(jQuery); - } -})(this, function($) { - var _ = function() { - "use strict"; - return { - isMsie: function() { - return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; - }, - isBlankString: function(str) { - return !str || /^\s*$/.test(str); - }, - escapeRegExChars: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - isString: function(obj) { - return typeof obj === "string"; - }, - isNumber: function(obj) { - return typeof obj === "number"; - }, - isArray: $.isArray, - isFunction: $.isFunction, - isObject: $.isPlainObject, - isUndefined: function(obj) { - return typeof obj === "undefined"; - }, - isElement: function(obj) { - return !!(obj && obj.nodeType === 1); - }, - isJQuery: function(obj) { - return obj instanceof $; - }, - toStr: function toStr(s) { - return _.isUndefined(s) || s === null ? "" : s + ""; - }, - bind: $.proxy, - each: function(collection, cb) { - $.each(collection, reverseArgs); - function reverseArgs(index, value) { - return cb(value, index); - } - }, - map: $.map, - filter: $.grep, - every: function(obj, test) { - var result = true; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (!(result = test.call(null, val, key, obj))) { - return false; - } - }); - return !!result; - }, - some: function(obj, test) { - var result = false; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (result = test.call(null, val, key, obj)) { - return false; - } - }); - return !!result; - }, - mixin: $.extend, - identity: function(x) { - return x; - }, - clone: function(obj) { - return $.extend(true, {}, obj); - }, - getIdGenerator: function() { - var counter = 0; - return function() { - return counter++; - }; - }, - templatify: function templatify(obj) { - return $.isFunction(obj) ? obj : template; - function template() { - return String(obj); - } - }, - defer: function(fn) { - setTimeout(fn, 0); - }, - debounce: function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments, later, callNow; - later = function() { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - } - }; - callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - } - return result; - }; - }, - throttle: function(func, wait) { - var context, args, timeout, result, previous, later; - previous = 0; - later = function() { - previous = new Date(); - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = new Date(), remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - stringify: function(val) { - return _.isString(val) ? val : JSON.stringify(val); - }, - noop: function() {} - }; - }(); - var WWW = function() { - "use strict"; - var defaultClassNames = { - wrapper: "twitter-typeahead", - input: "tt-input", - hint: "tt-hint", - menu: "tt-menu", - dataset: "tt-dataset", - suggestion: "tt-suggestion", - selectable: "tt-selectable", - empty: "tt-empty", - open: "tt-open", - cursor: "tt-cursor", - highlight: "tt-highlight" - }; - return build; - function build(o) { - var www, classes; - classes = _.mixin({}, defaultClassNames, o); - www = { - css: buildCss(), - classes: classes, - html: buildHtml(classes), - selectors: buildSelectors(classes) - }; - return { - css: www.css, - html: www.html, - classes: www.classes, - selectors: www.selectors, - mixin: function(o) { - _.mixin(o, www); - } - }; - } - function buildHtml(c) { - return { - wrapper: '', - menu: '
    ' - }; - } - function buildSelectors(classes) { - var selectors = {}; - _.each(classes, function(v, k) { - selectors[k] = "." + v; - }); - return selectors; - } - function buildCss() { - var css = { - wrapper: { - position: "relative", - display: "inline-block" - }, - hint: { - position: "absolute", - top: "0", - left: "0", - borderColor: "transparent", - boxShadow: "none", - opacity: "1" - }, - input: { - position: "relative", - verticalAlign: "top", - backgroundColor: "transparent" - }, - inputWithNoHint: { - position: "relative", - verticalAlign: "top" - }, - menu: { - position: "absolute", - top: "100%", - left: "0", - zIndex: "100", - display: "none" - }, - ltr: { - left: "0", - right: "auto" - }, - rtl: { - left: "auto", - right: " 0" - } - }; - if (_.isMsie()) { - _.mixin(css.input, { - backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" - }); +(function($) { + var VERSION = "0.9.3"; + var utils = { + isMsie: function() { + var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); + return match ? parseInt(match[2], 10) : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + bind: $.proxy, + bindAll: function(obj) { + var val; + for (var key in obj) { + $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); } - return css; - } + }, + indexOf: function(haystack, needle) { + for (var i = 0; i < haystack.length; i++) { + if (haystack[i] === needle) { + return i; + } + } + return -1; + }, + each: $.each, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + getUniqueId: function() { + var counter = 0; + return function() { + return counter++; + }; + }(), + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + tokenizeQuery: function(str) { + return $.trim(str).toLowerCase().split(/[\s]+/); + }, + tokenizeText: function(str) { + return $.trim(str).toLowerCase().split(/[\s\-_]+/); + }, + getProtocol: function() { + return location.protocol; + }, + noop: function() {} + }; + var EventTarget = function() { + var eventSplitter = /\s+/; + return { + on: function(events, callback) { + var event; + if (!callback) { + return this; + } + this._callbacks = this._callbacks || {}; + events = events.split(eventSplitter); + while (event = events.shift()) { + this._callbacks[event] = this._callbacks[event] || []; + this._callbacks[event].push(callback); + } + return this; + }, + trigger: function(events, data) { + var event, callbacks; + if (!this._callbacks) { + return this; + } + events = events.split(eventSplitter); + while (event = events.shift()) { + if (callbacks = this._callbacks[event]) { + for (var i = 0; i < callbacks.length; i += 1) { + callbacks[i].call(this, { + type: event, + data: data + }); + } + } + } + return this; + } + }; }(); var EventBus = function() { - "use strict"; - var namespace, deprecationMap; - namespace = "typeahead:"; - deprecationMap = { - render: "rendered", - cursorchange: "cursorchanged", - select: "selected", - autocomplete: "autocompleted" - }; + var namespace = "typeahead:"; function EventBus(o) { if (!o || !o.el) { $.error("EventBus initialized without el"); } this.$el = $(o.el); } - _.mixin(EventBus.prototype, { - _trigger: function(type, args) { - var $e; - $e = $.Event(namespace + type); - (args = args || []).unshift($e); - this.$el.trigger.apply(this.$el, args); - return $e; - }, - before: function(type) { - var args, $e; - args = [].slice.call(arguments, 1); - $e = this._trigger("before" + type, args); - return $e.isDefaultPrevented(); - }, + utils.mixin(EventBus.prototype, { trigger: function(type) { - var deprecatedType; - this._trigger(type, [].slice.call(arguments, 1)); - if (deprecatedType = deprecationMap[type]) { - this._trigger(deprecatedType, [].slice.call(arguments, 1)); - } + var args = [].slice.call(arguments, 1); + this.$el.trigger(namespace + type, args); } }); return EventBus; }(); - var EventEmitter = function() { - "use strict"; - var splitter = /\s+/, nextTick = getNextTick(); - return { - onSync: onSync, - onAsync: onAsync, - off: off, - trigger: trigger - }; - function on(method, types, cb, context) { - var type; - if (!cb) { - return this; - } - types = types.split(splitter); - cb = context ? bindContext(cb, context) : cb; - this._callbacks = this._callbacks || {}; - while (type = types.shift()) { - this._callbacks[type] = this._callbacks[type] || { - sync: [], - async: [] - }; - this._callbacks[type][method].push(cb); - } - return this; + var PersistentStorage = function() { + var ls, methods; + try { + ls = window.localStorage; + ls.setItem("~~~", "!"); + ls.removeItem("~~~"); + } catch (err) { + ls = null; } - function onAsync(types, cb, context) { - return on.call(this, "async", types, cb, context); + function PersistentStorage(namespace) { + this.prefix = [ "__", namespace, "__" ].join(""); + this.ttlKey = "__ttl__"; + this.keyMatcher = new RegExp("^" + this.prefix); } - function onSync(types, cb, context) { - return on.call(this, "sync", types, cb, context); - } - function off(types) { - var type; - if (!this._callbacks) { - return this; - } - types = types.split(splitter); - while (type = types.shift()) { - delete this._callbacks[type]; - } - return this; - } - function trigger(types) { - var type, callbacks, args, syncFlush, asyncFlush; - if (!this._callbacks) { - return this; - } - types = types.split(splitter); - args = [].slice.call(arguments, 1); - while ((type = types.shift()) && (callbacks = this._callbacks[type])) { - syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); - asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); - syncFlush() && nextTick(asyncFlush); - } - return this; - } - function getFlush(callbacks, context, args) { - return flush; - function flush() { - var cancelled; - for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { - cancelled = callbacks[i].apply(context, args) === false; + if (ls && window.JSON) { + methods = { + _prefix: function(key) { + return this.prefix + key; + }, + _ttlKey: function(key) { + return this._prefix(key) + this.ttlKey; + }, + get: function(key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(ls.getItem(this._prefix(key))); + }, + set: function(key, val, ttl) { + if (utils.isNumber(ttl)) { + ls.setItem(this._ttlKey(key), encode(now() + ttl)); + } else { + ls.removeItem(this._ttlKey(key)); + } + return ls.setItem(this._prefix(key), encode(val)); + }, + remove: function(key) { + ls.removeItem(this._ttlKey(key)); + ls.removeItem(this._prefix(key)); + return this; + }, + clear: function() { + var i, key, keys = [], len = ls.length; + for (i = 0; i < len; i++) { + if ((key = ls.key(i)).match(this.keyMatcher)) { + keys.push(key.replace(this.keyMatcher, "")); + } + } + for (i = keys.length; i--; ) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function(key) { + var ttl = decode(ls.getItem(this._ttlKey(key))); + return utils.isNumber(ttl) && now() > ttl ? true : false; } - return !cancelled; - } - } - function getNextTick() { - var nextTickFn; - if (window.setImmediate) { - nextTickFn = function nextTickSetImmediate(fn) { - setImmediate(function() { - fn(); - }); - }; - } else { - nextTickFn = function nextTickSetTimeout(fn) { - setTimeout(function() { - fn(); - }, 0); - }; - } - return nextTickFn; - } - function bindContext(fn, context) { - return fn.bind ? fn.bind(context) : function() { - fn.apply(context, [].slice.call(arguments, 0)); + }; + } else { + methods = { + get: utils.noop, + set: utils.noop, + remove: utils.noop, + clear: utils.noop, + isExpired: utils.noop }; } + utils.mixin(PersistentStorage.prototype, methods); + return PersistentStorage; + function now() { + return new Date().getTime(); + } + function encode(val) { + return JSON.stringify(utils.isUndefined(val) ? null : val); + } + function decode(val) { + return JSON.parse(val); + } }(); - var highlight = function(doc) { - "use strict"; - var defaults = { - node: null, - pattern: null, - tagName: "strong", - className: null, - wordsOnly: false, - caseSensitive: false - }; - return function hightlight(o) { - var regex; - o = _.mixin({}, defaults, o); - if (!o.node || !o.pattern) { - return; - } - o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; - regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); - traverse(o.node, hightlightTextNode); - function hightlightTextNode(textNode) { - var match, patternNode, wrapperNode; - if (match = regex.exec(textNode.data)) { - wrapperNode = doc.createElement(o.tagName); - o.className && (wrapperNode.className = o.className); - patternNode = textNode.splitText(match.index); - patternNode.splitText(match[0].length); - wrapperNode.appendChild(patternNode.cloneNode(true)); - textNode.parentNode.replaceChild(wrapperNode, patternNode); + var RequestCache = function() { + function RequestCache(o) { + utils.bindAll(this); + o = o || {}; + this.sizeLimit = o.sizeLimit || 10; + this.cache = {}; + this.cachedKeysByAge = []; + } + utils.mixin(RequestCache.prototype, { + get: function(url) { + return this.cache[url]; + }, + set: function(url, resp) { + var requestToEvict; + if (this.cachedKeysByAge.length === this.sizeLimit) { + requestToEvict = this.cachedKeysByAge.shift(); + delete this.cache[requestToEvict]; } - return !!match; + this.cache[url] = resp; + this.cachedKeysByAge.push(url); } - function traverse(el, hightlightTextNode) { - var childNode, TEXT_NODE_TYPE = 3; - for (var i = 0; i < el.childNodes.length; i++) { - childNode = el.childNodes[i]; - if (childNode.nodeType === TEXT_NODE_TYPE) { - i += hightlightTextNode(childNode) ? 1 : 0; - } else { - traverse(childNode, hightlightTextNode); + }); + return RequestCache; + }(); + var Transport = function() { + var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; + function Transport(o) { + utils.bindAll(this); + o = utils.isString(o) ? { + url: o + } : o; + requestCache = requestCache || new RequestCache(); + maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; + this.url = o.url; + this.wildcard = o.wildcard || "%QUERY"; + this.filter = o.filter; + this.replace = o.replace; + this.ajaxSettings = { + type: "get", + cache: o.cache, + timeout: o.timeout, + dataType: o.dataType || "json", + beforeSend: o.beforeSend + }; + this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); + } + utils.mixin(Transport.prototype, { + _get: function(url, cb) { + var that = this; + if (belowPendingRequestsThreshold()) { + this._sendRequest(url).done(done); + } else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + function done(resp) { + var data = that.filter ? that.filter(resp) : resp; + cb && cb(data); + requestCache.set(url, resp); + } + }, + _sendRequest: function(url) { + var that = this, jqXhr = pendingRequests[url]; + if (!jqXhr) { + incrementPendingRequests(); + jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); + } + return jqXhr; + function always() { + decrementPendingRequests(); + pendingRequests[url] = null; + if (that.onDeckRequestArgs) { + that._get.apply(that, that.onDeckRequestArgs); + that.onDeckRequestArgs = null; } } - } - }; - function getRegex(patterns, caseSensitive, wordsOnly) { - var escapedPatterns = [], regexStr; - for (var i = 0, len = patterns.length; i < len; i++) { - escapedPatterns.push(_.escapeRegExChars(patterns[i])); - } - regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; - return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); - } - }(window.document); - var Input = function() { - "use strict"; - var specialKeyCodeMap; - specialKeyCodeMap = { - 9: "tab", - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - function Input(o, www) { - o = o || {}; - if (!o.input) { - $.error("input is missing"); - } - www.mixin(this); - this.$hint = $(o.hint); - this.$input = $(o.input); - this.query = this.$input.val(); - this.queryWhenFocused = this.hasFocus() ? this.query : null; - this.$overflowHelper = buildOverflowHelper(this.$input); - this._checkLanguageDirection(); - if (this.$hint.length === 0) { - this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; - } - } - Input.normalizeQuery = function(str) { - return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - }; - _.mixin(Input.prototype, EventEmitter, { - _onBlur: function onBlur() { - this.resetInputValue(); - this.trigger("blurred"); }, - _onFocus: function onFocus() { - this.queryWhenFocused = this.query; + get: function(query, cb) { + var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; + cb = cb || utils.noop; + url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); + if (resp = requestCache.get(url)) { + utils.defer(function() { + cb(that.filter ? that.filter(resp) : resp); + }); + } else { + this._get(url, cb); + } + return !!resp; + } + }); + return Transport; + function incrementPendingRequests() { + pendingRequestsCount++; + } + function decrementPendingRequests() { + pendingRequestsCount--; + } + function belowPendingRequestsThreshold() { + return pendingRequestsCount < maxPendingRequests; + } + }(); + var Dataset = function() { + var keys = { + thumbprint: "thumbprint", + protocol: "protocol", + itemHash: "itemHash", + adjacencyList: "adjacencyList" + }; + function Dataset(o) { + utils.bindAll(this); + if (utils.isString(o.template) && !o.engine) { + $.error("no template engine specified"); + } + if (!o.local && !o.prefetch && !o.remote) { + $.error("one of local, prefetch, or remote is required"); + } + this.name = o.name || utils.getUniqueId(); + this.limit = o.limit || 5; + this.minLength = o.minLength || 1; + this.header = o.header; + this.footer = o.footer; + this.valueKey = o.valueKey || "value"; + this.template = compileTemplate(o.template, o.engine, this.valueKey); + this.local = o.local; + this.prefetch = o.prefetch; + this.remote = o.remote; + this.itemHash = {}; + this.adjacencyList = {}; + this.storage = o.name ? new PersistentStorage(o.name) : null; + } + utils.mixin(Dataset.prototype, { + _processLocalData: function(data) { + this._mergeProcessedData(this._processData(data)); + }, + _loadPrefetchData: function(o) { + var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; + if (this.storage) { + storedThumbprint = this.storage.get(keys.thumbprint); + storedProtocol = this.storage.get(keys.protocol); + storedItemHash = this.storage.get(keys.itemHash); + storedAdjacencyList = this.storage.get(keys.adjacencyList); + } + isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); + o = utils.isString(o) ? { + url: o + } : o; + o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; + if (storedItemHash && storedAdjacencyList && !isExpired) { + this._mergeProcessedData({ + itemHash: storedItemHash, + adjacencyList: storedAdjacencyList + }); + deferred = $.Deferred().resolve(); + } else { + deferred = $.getJSON(o.url).done(processPrefetchData); + } + return deferred; + function processPrefetchData(data) { + var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; + if (that.storage) { + that.storage.set(keys.itemHash, itemHash, o.ttl); + that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); + that.storage.set(keys.thumbprint, thumbprint, o.ttl); + that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); + } + that._mergeProcessedData(processedData); + } + }, + _transformDatum: function(datum) { + var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { + value: value, + tokens: tokens + }; + if (utils.isString(datum)) { + item.datum = {}; + item.datum[this.valueKey] = datum; + } else { + item.datum = datum; + } + item.tokens = utils.filter(item.tokens, function(token) { + return !utils.isBlankString(token); + }); + item.tokens = utils.map(item.tokens, function(token) { + return token.toLowerCase(); + }); + return item; + }, + _processData: function(data) { + var that = this, itemHash = {}, adjacencyList = {}; + utils.each(data, function(i, datum) { + var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); + itemHash[id] = item; + utils.each(item.tokens, function(i, token) { + var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); + !~utils.indexOf(adjacency, id) && adjacency.push(id); + }); + }); + return { + itemHash: itemHash, + adjacencyList: adjacencyList + }; + }, + _mergeProcessedData: function(processedData) { + var that = this; + utils.mixin(this.itemHash, processedData.itemHash); + utils.each(processedData.adjacencyList, function(character, adjacency) { + var masterAdjacency = that.adjacencyList[character]; + that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; + }); + }, + _getLocalSuggestions: function(terms) { + var that = this, firstChars = [], lists = [], shortestList, suggestions = []; + utils.each(terms, function(i, term) { + var firstChar = term.charAt(0); + !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); + }); + utils.each(firstChars, function(i, firstChar) { + var list = that.adjacencyList[firstChar]; + if (!list) { + return false; + } + lists.push(list); + if (!shortestList || list.length < shortestList.length) { + shortestList = list; + } + }); + if (lists.length < firstChars.length) { + return []; + } + utils.each(shortestList, function(i, id) { + var item = that.itemHash[id], isCandidate, isMatch; + isCandidate = utils.every(lists, function(list) { + return ~utils.indexOf(list, id); + }); + isMatch = isCandidate && utils.every(terms, function(term) { + return utils.some(item.tokens, function(token) { + return token.indexOf(term) === 0; + }); + }); + isMatch && suggestions.push(item); + }); + return suggestions; + }, + initialize: function() { + var deferred; + this.local && this._processLocalData(this.local); + this.transport = this.remote ? new Transport(this.remote) : null; + deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); + this.local = this.prefetch = this.remote = null; + this.initialize = function() { + return deferred; + }; + return deferred; + }, + getSuggestions: function(query, cb) { + var that = this, terms, suggestions, cacheHit = false; + if (query.length < this.minLength) { + return; + } + terms = utils.tokenizeQuery(query); + suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); + if (suggestions.length < this.limit && this.transport) { + cacheHit = this.transport.get(query, processRemoteData); + } + !cacheHit && cb && cb(suggestions); + function processRemoteData(data) { + suggestions = suggestions.slice(0); + utils.each(data, function(i, datum) { + var item = that._transformDatum(datum), isDuplicate; + isDuplicate = utils.some(suggestions, function(suggestion) { + //return item.value === suggestion.value; + return false; + }); + !isDuplicate && suggestions.push(item); + return suggestions.length < that.limit; + }); + cb && cb(suggestions); + } + } + }); + return Dataset; + function compileTemplate(template, engine, valueKey) { + var renderFn, compiledTemplate; + if (utils.isFunction(template)) { + renderFn = template; + } else if (utils.isString(template)) { + compiledTemplate = engine.compile(template); + renderFn = utils.bind(compiledTemplate.render, compiledTemplate); + } else { + renderFn = function(context) { + return "

    " + context[valueKey] + "

    "; + }; + } + return renderFn; + } + }(); + var InputView = function() { + function InputView(o) { + var that = this; + utils.bindAll(this); + this.specialKeyCodeMap = { + // START METAMAPS CODE + //9: "tab", + // END METAMAPS CODE + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + this.$hint = $(o.hint); + this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); + if (!utils.isMsie()) { + this.$input.on("input.tt", this._compareQueryToInputValue); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + utils.defer(that._compareQueryToInputValue); + }); + } + this.query = this.$input.val(); + this.$overflowHelper = buildOverflowHelper(this.$input); + } + utils.mixin(InputView.prototype, EventTarget, { + _handleFocus: function() { this.trigger("focused"); }, - _onKeydown: function onKeydown($e) { - var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; - this._managePreventDefault(keyName, $e); - if (keyName && this._shouldTrigger(keyName, $e)) { - this.trigger(keyName + "Keyed", $e); - } + _handleBlur: function() { + this.trigger("blured"); }, - _onInput: function onInput() { - this._setQuery(this.getInputValue()); - this.clearHintIfInvalid(); - this._checkLanguageDirection(); + _handleSpecialKeyEvent: function($e) { + var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; + keyName && this.trigger(keyName + "Keyed", $e); }, - _managePreventDefault: function managePreventDefault(keyName, $e) { - var preventDefault; - switch (keyName) { - case "up": - case "down": - preventDefault = !withModifier($e); - break; - - default: - preventDefault = false; - } - preventDefault && $e.preventDefault(); - }, - _shouldTrigger: function shouldTrigger(keyName, $e) { - var trigger; - switch (keyName) { - case "tab": - trigger = !withModifier($e); - break; - - default: - trigger = true; - } - return trigger; - }, - _checkLanguageDirection: function checkLanguageDirection() { - var dir = (this.$input.css("direction") || "ltr").toLowerCase(); - if (this.dir !== dir) { - this.dir = dir; - this.$hint.attr("dir", dir); - this.trigger("langDirChanged", dir); - } - }, - _setQuery: function setQuery(val, silent) { - var areEquivalent, hasDifferentWhitespace; - areEquivalent = areQueriesEquivalent(val, this.query); - hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; - this.query = val; - if (!silent && !areEquivalent) { - this.trigger("queryChanged", this.query); - } else if (!silent && hasDifferentWhitespace) { - this.trigger("whitespaceChanged", this.query); - } - }, - bind: function() { - var that = this, onBlur, onFocus, onKeydown, onInput; - onBlur = _.bind(this._onBlur, this); - onFocus = _.bind(this._onFocus, this); - onKeydown = _.bind(this._onKeydown, this); - onInput = _.bind(this._onInput, this); - this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); - if (!_.isMsie() || _.isMsie() > 9) { - this.$input.on("input.tt", onInput); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - _.defer(_.bind(that._onInput, that, $e)); + _compareQueryToInputValue: function() { + var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; + if (isSameQueryExceptWhitespace) { + this.trigger("whitespaceChanged", { + value: this.query + }); + } else if (!isSameQuery) { + this.trigger("queryChanged", { + value: this.query = inputValue }); } - return this; }, - focus: function focus() { + destroy: function() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$hint = this.$input = this.$overflowHelper = null; + }, + focus: function() { this.$input.focus(); }, - blur: function blur() { + blur: function() { this.$input.blur(); }, - getLangDir: function getLangDir() { - return this.dir; + getQuery: function() { + return this.query; }, - getQuery: function getQuery() { - return this.query || ""; + setQuery: function(query) { + this.query = query; }, - setQuery: function setQuery(val, silent) { - this.setInputValue(val); - this._setQuery(val, silent); - }, - hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { - return this.query !== this.queryWhenFocused; - }, - getInputValue: function getInputValue() { + getInputValue: function() { return this.$input.val(); }, - setInputValue: function setInputValue(value) { + setInputValue: function(value, silent) { this.$input.val(value); - this.clearHintIfInvalid(); - this._checkLanguageDirection(); + !silent && this._compareQueryToInputValue(); }, - resetInputValue: function resetInputValue() { - this.setInputValue(this.query); - }, - getHint: function getHint() { + getHintValue: function() { return this.$hint.val(); }, - setHint: function setHint(value) { + setHintValue: function(value) { this.$hint.val(value); }, - clearHint: function clearHint() { - this.setHint(""); + getLanguageDirection: function() { + return (this.$input.css("direction") || "ltr").toLowerCase(); }, - clearHintIfInvalid: function clearHintIfInvalid() { - var val, hint, valIsPrefixOfHint, isValid; - val = this.getInputValue(); - hint = this.getHint(); - valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; - isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); - !isValid && this.clearHint(); - }, - hasFocus: function hasFocus() { - return this.$input.is(":focus"); - }, - hasOverflow: function hasOverflow() { - var constraint = this.$input.width() - 2; + isOverflow: function() { this.$overflowHelper.text(this.getInputValue()); - return this.$overflowHelper.width() >= constraint; + return this.$overflowHelper.width() > this.$input.width(); }, isCursorAtEnd: function() { - var valueLength, selectionStart, range; - valueLength = this.$input.val().length; - selectionStart = this.$input[0].selectionStart; - if (_.isNumber(selectionStart)) { + var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; + if (utils.isNumber(selectionStart)) { return selectionStart === valueLength; } else if (document.selection) { range = document.selection.createRange(); @@ -610,20 +661,15 @@ return valueLength === range.text.length; } return true; - }, - destroy: function destroy() { - this.$hint.off(".tt"); - this.$input.off(".tt"); - this.$overflowHelper.remove(); - this.$hint = this.$input = this.$overflowHelper = $("
    "); } }); - return Input; + return InputView; function buildOverflowHelper($input) { - return $('').css({ + return $("").css({ position: "absolute", + left: "-9999px", visibility: "hidden", - whiteSpace: "pre", + whiteSpace: "nowrap", fontFamily: $input.css("font-family"), fontSize: $input.css("font-size"), fontStyle: $input.css("font-style"), @@ -636,903 +682,484 @@ textTransform: $input.css("text-transform") }).insertAfter($input); } - function areQueriesEquivalent(a, b) { - return Input.normalizeQuery(a) === Input.normalizeQuery(b); - } - function withModifier($e) { - return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; + function compareQueries(a, b) { + a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + return a === b; } }(); - var Dataset = function() { - "use strict"; - var keys, nameGenerator; - keys = { - val: "tt-selectable-display", - obj: "tt-selectable-object" + var DropdownView = function() { + var html = { + suggestionsList: '' + }, css = { + suggestionsList: { + display: "block" + }, + suggestion: { + whiteSpace: "nowrap", + cursor: "pointer" + }, + suggestionChild: { + whiteSpace: "normal" + } }; - nameGenerator = _.getIdGenerator(); - function Dataset(o, www) { - o = o || {}; - o.templates = o.templates || {}; - o.templates.notFound = o.templates.notFound || o.templates.empty; - if (!o.source) { - $.error("missing source"); - } - if (!o.node) { - $.error("missing node"); - } - if (o.name && !isValidName(o.name)) { - $.error("invalid dataset name: " + o.name); - } - www.mixin(this); - this.highlight = !!o.highlight; - this.name = o.name || nameGenerator(); - this.limit = o.limit || 5; - this.displayFn = getDisplayFn(o.display || o.displayKey); - this.templates = getTemplates(o.templates, this.displayFn); - this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; - this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; - this._resetLastSuggestion(); - this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); + function DropdownView(o) { + utils.bindAll(this); + this.isOpen = false; + this.isEmpty = true; + this.isMouseOverDropdown = false; + this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover); } - Dataset.extractData = function extractData(el) { - var $el = $(el); - if ($el.data(keys.obj)) { - return { - val: $el.data(keys.val) || "", - obj: $el.data(keys.obj) || null + utils.mixin(DropdownView.prototype, EventTarget, { + _handleMouseenter: function() { + this.isMouseOverDropdown = true; + }, + _handleMouseleave: function() { + this.isMouseOverDropdown = false; + + // START METAMAPS CODE + this._getSuggestions().removeClass("tt-is-under-cursor"); + this._getSuggestions().removeClass("tt-is-under-mouse-cursor"); + // END METAMAPS CODE + }, + _handleMouseover: function($e) { + var $suggestion = $($e.currentTarget); + this._getSuggestions().removeClass("tt-is-under-cursor"); + // START METAMAPS CODE + this._getSuggestions().removeClass("tt-is-under-mouse-cursor"); + $suggestion.addClass("tt-is-under-mouse-cursor"); + // ORIGINAL CODE $suggestion.addClass("tt-is-under-cursor"); + }, + _handleSelection: function($e) { + var $suggestion = $($e.currentTarget); + this.trigger("suggestionSelected", extractSuggestion($suggestion)); + }, + _show: function() { + this.$menu.css("display", "block"); + }, + _hide: function() { + this.$menu.hide(); + }, + _moveCursor: function(increment) { + var $suggestions, $cur, nextIndex, $underCursor; + if (!this.isVisible()) { + return; + } + $suggestions = this._getSuggestions(); + $cur = $suggestions.filter(".tt-is-under-cursor"); + $cur.removeClass("tt-is-under-cursor"); + nextIndex = $suggestions.index($cur) + increment; + nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; + if (nextIndex === -1) { + this.trigger("cursorRemoved"); + return; + } else if (nextIndex < -1) { + nextIndex = $suggestions.length - 1; + } + $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); + this._ensureVisibility($underCursor); + this.trigger("cursorMoved", extractSuggestion($underCursor)); + }, + _getSuggestions: function() { + return this.$menu.find(".tt-suggestions > .tt-suggestion"); + }, + _ensureVisibility: function($el) { + var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true); + if (elTop < 0) { + this.$menu.scrollTop(menuScrollTop + elTop); + } else if (menuHeight < elBottom) { + this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); + } + }, + destroy: function() { + this.$menu.off(".tt"); + this.$menu = null; + }, + isVisible: function() { + return this.isOpen && !this.isEmpty; + }, + closeUnlessMouseIsOverDropdown: function() { + if (!this.isMouseOverDropdown) { + this.close(); + } + }, + close: function() { + if (this.isOpen) { + this.isOpen = false; + this.isMouseOverDropdown = false; + this._hide(); + this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); + this.trigger("closed"); + } + }, + open: function() { + if (!this.isOpen) { + this.isOpen = true; + !this.isEmpty && this._show(); + this.trigger("opened"); + } + }, + setLanguageDirection: function(dir) { + var ltrCss = { + left: "0", + right: "auto" + }, rtlCss = { + left: "auto", + right: " 0" }; - } - return null; - }; - _.mixin(Dataset.prototype, EventEmitter, { - _overwrite: function overwrite(query, suggestions) { - suggestions = suggestions || []; - if (suggestions.length) { - this._renderSuggestions(query, suggestions); - } else if (this.async && this.templates.pending) { - this._renderPending(query); - } else if (!this.async && this.templates.notFound) { - this._renderNotFound(query); + dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); + }, + moveCursorUp: function() { + this._moveCursor(-1); + }, + moveCursorDown: function() { + this._moveCursor(+1); + }, + getSuggestionUnderCursor: function() { + var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); + return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; + }, + getFirstSuggestion: function() { + var $suggestion = this._getSuggestions().first(); + return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; + }, + renderSuggestions: function(dataset, suggestions) { + var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '
    %body
    ', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el; + if ($dataset.length === 0) { + $suggestionsList = $(html.suggestionsList).css(css.suggestionsList); + $dataset = $("
    ").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu); + } + if (suggestions.length > 0) { + this.isEmpty = false; + this.isOpen && this._show(); + elBuilder = document.createElement("div"); + fragment = document.createDocumentFragment(); + utils.each(suggestions, function(i, suggestion) { + suggestion.dataset = dataset.name; + compiledHtml = dataset.template(suggestion.datum); + elBuilder.innerHTML = wrapper.replace("%body", compiledHtml); + $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion); + $el.children().each(function() { + $(this).css(css.suggestionChild); + }); + fragment.appendChild($el[0]); + }); + $dataset.show().find(".tt-suggestions").html(fragment); } else { - this._empty(); + this.clearSuggestions(dataset.name); } - this.trigger("rendered", this.name, suggestions, false); + this.trigger("suggestionsRendered"); }, - _append: function append(query, suggestions) { - suggestions = suggestions || []; - if (suggestions.length && this.$lastSuggestion.length) { - this._appendSuggestions(query, suggestions); - } else if (suggestions.length) { - this._renderSuggestions(query, suggestions); - } else if (!this.$lastSuggestion.length && this.templates.notFound) { - this._renderNotFound(query); + clearSuggestions: function(datasetName) { + var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions"); + $datasets.hide(); + $suggestions.empty(); + if (this._getSuggestions().length === 0) { + this.isEmpty = true; + this._hide(); } - this.trigger("rendered", this.name, suggestions, true); + } + }); + return DropdownView; + function extractSuggestion($el) { + return $el.data("suggestion"); + } + }(); + var TypeaheadView = function() { + var html = { + wrapper: '', + hint: '', + dropdown: '' + }, css = { + wrapper: { + position: "relative", + display: "inline-block" }, - _renderSuggestions: function renderSuggestions(query, suggestions) { - var $fragment; - $fragment = this._getSuggestionsFragment(query, suggestions); - this.$lastSuggestion = $fragment.children().last(); - this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none" }, - _appendSuggestions: function appendSuggestions(query, suggestions) { - var $fragment, $lastSuggestion; - $fragment = this._getSuggestionsFragment(query, suggestions); - $lastSuggestion = $fragment.children().last(); - this.$lastSuggestion.after($fragment); - this.$lastSuggestion = $lastSuggestion; + query: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" }, - _renderPending: function renderPending(query) { - var template = this.templates.pending; - this._resetLastSuggestion(); - template && this.$el.html(template({ - query: query, - dataset: this.name - })); + dropdown: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + } + }; + if (utils.isMsie()) { + utils.mixin(css.query, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + if (utils.isMsie() && utils.isMsie() <= 7) { + utils.mixin(css.wrapper, { + display: "inline", + zoom: "1" + }); + utils.mixin(css.query, { + marginTop: "-1px" + }); + } + function TypeaheadView(o) { + var $menu, $input, $hint; + utils.bindAll(this); + this.$node = buildDomStructure(o.input); + this.datasets = o.datasets; + this.dir = null; + this.eventBus = o.eventBus; + $menu = this.$node.find(".tt-dropdown-menu"); + $input = this.$node.find(".tt-query"); + $hint = this.$node.find(".tt-hint"); + this.dropdownView = new DropdownView({ + menu: $menu + }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent); + // START METAMAPS CODE + this.dropdownView.on('suggestionsRendered', this._suggestionsRendered); + // END METAMAPS CODE + + this.inputView = new InputView({ + input: $input, + hint: $hint + }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete); + // START METAMAPS CODE + this.inputView.on('queryChanged', this._queryChanged); + // END METAMAPS CODE + } + utils.mixin(TypeaheadView.prototype, EventTarget, { + _managePreventDefault: function(e) { + var $e = e.data, hint, inputValue, preventDefault = false; + switch (e.type) { + case "tabKeyed": + hint = this.inputView.getHintValue(); + inputValue = this.inputView.getInputValue(); + preventDefault = hint && hint !== inputValue; + break; + + case "upKeyed": + case "downKeyed": + preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey; + break; + } + preventDefault && $e.preventDefault(); }, - _renderNotFound: function renderNotFound(query) { - var template = this.templates.notFound; - this._resetLastSuggestion(); - template && this.$el.html(template({ - query: query, - dataset: this.name - })); + _setLanguageDirection: function() { + var dir = this.inputView.getLanguageDirection(); + if (dir !== this.dir) { + this.dir = dir; + this.$node.css("direction", dir); + this.dropdownView.setLanguageDirection(dir); + } }, - _empty: function empty() { - this.$el.empty(); - this._resetLastSuggestion(); + // START METAMAPS CODE + _suggestionsRendered: function() { + this.eventBus.trigger('suggestionsRendered'); }, - _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { - var that = this, fragment; - fragment = document.createDocumentFragment(); - _.each(suggestions, function getSuggestionNode(suggestion) { - var $el, context; - context = that._injectQuery(query, suggestion); - $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); - fragment.appendChild($el[0]); + _queryChanged: function() { + this.eventBus.trigger('queryChanged'); + }, + // END METAMAPS CODE + _updateHint: function() { + var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match; + if (hint && dropdownIsVisible && !inputHasOverflow) { + inputValue = this.inputView.getInputValue(); + query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, ""); + escapedQuery = utils.escapeRegExChars(query); + beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); + match = beginsWithQuery.exec(hint); + this.inputView.setHintValue(inputValue + (match ? match[1] : "")); + } + }, + _clearHint: function() { + this.inputView.setHintValue(""); + }, + _clearSuggestions: function() { + this.dropdownView.clearSuggestions(); + }, + _setInputValueToQuery: function() { + this.inputView.setInputValue(this.inputView.getQuery()); + }, + _setInputValueToSuggestionUnderCursor: function(e) { + var suggestion = e.data; + this.inputView.setInputValue(suggestion.value, true); + }, + _openDropdown: function() { + this.dropdownView.open(); + }, + _closeDropdown: function(e) { + this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"](); + }, + _moveDropdownCursor: function(e) { + var $e = e.data; + if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) { + this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"](); + } + }, + _handleSelection: function(e) { + var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor(); + if (suggestion) { + this.inputView.setInputValue(suggestion.value); + byClick ? this.inputView.focus() : e.data.preventDefault(); + byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close(); + this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset); + } + }, + _getSuggestions: function() { + var that = this, query = this.inputView.getQuery(); + if (utils.isBlankString(query)) { + return; + } + utils.each(this.datasets, function(i, dataset) { + dataset.getSuggestions(query, function(suggestions) { + if (query === that.inputView.getQuery()) { + that.dropdownView.renderSuggestions(dataset, suggestions); + } + }); }); - this.highlight && highlight({ - className: this.classes.highlight, - node: fragment, - pattern: query - }); - return $(fragment); }, - _getFooter: function getFooter(query, suggestions) { - return this.templates.footer ? this.templates.footer({ - query: query, - suggestions: suggestions, - dataset: this.name - }) : null; - }, - _getHeader: function getHeader(query, suggestions) { - return this.templates.header ? this.templates.header({ - query: query, - suggestions: suggestions, - dataset: this.name - }) : null; - }, - _resetLastSuggestion: function resetLastSuggestion() { - this.$lastSuggestion = $(); - }, - _injectQuery: function injectQuery(query, obj) { - return _.isObject(obj) ? _.mixin({ - _query: query - }, obj) : obj; - }, - update: function update(query) { - var that = this, canceled = false, syncCalled = false, rendered = 0; - this.cancel(); - this.cancel = function cancel() { - canceled = true; - that.cancel = $.noop; - that.async && that.trigger("asyncCanceled", query); - }; - this.source(query, sync, async); - !syncCalled && sync([]); - function sync(suggestions) { - if (syncCalled) { + _autocomplete: function(e) { + var isCursorAtEnd, ignoreEvent, query, hint, suggestion; + if (e.type === "rightKeyed" || e.type === "leftKeyed") { + isCursorAtEnd = this.inputView.isCursorAtEnd(); + ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed"; + if (!isCursorAtEnd || ignoreEvent) { return; } - syncCalled = true; - suggestions = (suggestions || []).slice(0, that.limit); - rendered = suggestions.length; - that._overwrite(query, suggestions); - if (rendered < that.limit && that.async) { - that.trigger("asyncRequested", query); - } } - function async(suggestions) { - suggestions = suggestions || []; - if (!canceled && rendered < that.limit) { - that.cancel = $.noop; - rendered += suggestions.length; - that._append(query, suggestions.slice(0, that.limit - rendered)); - that.async && that.trigger("asyncReceived", query); - } + query = this.inputView.getQuery(); + hint = this.inputView.getHintValue(); + if (hint !== "" && query !== hint) { + suggestion = this.dropdownView.getFirstSuggestion(); + this.inputView.setInputValue(suggestion.value); + this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset); } }, - cancel: $.noop, - clear: function clear() { - this._empty(); - this.cancel(); - this.trigger("cleared"); + _propagateEvent: function(e) { + this.eventBus.trigger(e.type); }, - isEmpty: function isEmpty() { - return this.$el.is(":empty"); + destroy: function() { + this.inputView.destroy(); + this.dropdownView.destroy(); + destroyDomStructure(this.$node); + this.$node = null; }, - destroy: function destroy() { - this.$el = $("
    "); + setQuery: function(query) { + this.inputView.setQuery(query); + this.inputView.setInputValue(query); + this._clearHint(); + this._clearSuggestions(); + this._getSuggestions(); } }); - return Dataset; - function getDisplayFn(display) { - display = display || _.stringify; - return _.isFunction(display) ? display : displayFn; - function displayFn(obj) { - return obj[display]; - } + return TypeaheadView; + function buildDomStructure(input) { + var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint); + $wrapper = $wrapper.css(css.wrapper); + $dropdown = $dropdown.css(css.dropdown); + $hint.css(css.hint).css({ + backgroundAttachment: $input.css("background-attachment"), + backgroundClip: $input.css("background-clip"), + backgroundColor: $input.css("background-color"), + backgroundImage: $input.css("background-image"), + backgroundOrigin: $input.css("background-origin"), + backgroundPosition: $input.css("background-position"), + backgroundRepeat: $input.css("background-repeat"), + backgroundSize: $input.css("background-size") + }); + $input.data("ttAttrs", { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass("tt-query").attr({ + autocomplete: "off", + spellcheck: false + }).css(css.query); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input.wrap($wrapper).parent().prepend($hint).append($dropdown); } - function getTemplates(templates, displayFn) { - return { - notFound: templates.notFound && _.templatify(templates.notFound), - pending: templates.pending && _.templatify(templates.pending), - header: templates.header && _.templatify(templates.header), - footer: templates.footer && _.templatify(templates.footer), - suggestion: templates.suggestion || suggestionTemplate - }; - function suggestionTemplate(context) { - return $("
    ").text(displayFn(context)); - } - } - function isValidName(str) { - return /^[_a-zA-Z0-9-]+$/.test(str); - } - }(); - var Menu = function() { - "use strict"; - function Menu(o, www) { - var that = this; - o = o || {}; - if (!o.node) { - $.error("node is required"); - } - www.mixin(this); - this.$node = $(o.node); - this.query = null; - this.datasets = _.map(o.datasets, initializeDataset); - function initializeDataset(oDataset) { - var node = that.$node.find(oDataset.node).first(); - oDataset.node = node.length ? node : $("
    ").appendTo(that.$node); - return new Dataset(oDataset, www); - } - } - _.mixin(Menu.prototype, EventEmitter, { - _onSelectableClick: function onSelectableClick($e) { - this.trigger("selectableClicked", $($e.currentTarget)); - }, - _onRendered: function onRendered(type, dataset, suggestions, async) { - this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); - this.trigger("datasetRendered", dataset, suggestions, async); - }, - _onCleared: function onCleared() { - this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); - this.trigger("datasetCleared"); - }, - _propagate: function propagate() { - this.trigger.apply(this, arguments); - }, - _allDatasetsEmpty: function allDatasetsEmpty() { - return _.every(this.datasets, isDatasetEmpty); - function isDatasetEmpty(dataset) { - return dataset.isEmpty(); - } - }, - _getSelectables: function getSelectables() { - return this.$node.find(this.selectors.selectable); - }, - _removeCursor: function _removeCursor() { - var $selectable = this.getActiveSelectable(); - $selectable && $selectable.removeClass(this.classes.cursor); - }, - _ensureVisible: function ensureVisible($el) { - var elTop, elBottom, nodeScrollTop, nodeHeight; - elTop = $el.position().top; - elBottom = elTop + $el.outerHeight(true); - nodeScrollTop = this.$node.scrollTop(); - nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); - if (elTop < 0) { - this.$node.scrollTop(nodeScrollTop + elTop); - } else if (nodeHeight < elBottom) { - this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); - } - }, - bind: function() { - var that = this, onSelectableClick; - onSelectableClick = _.bind(this._onSelectableClick, this); - this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); - _.each(this.datasets, function(dataset) { - dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); - }); - return this; - }, - isOpen: function isOpen() { - return this.$node.hasClass(this.classes.open); - }, - open: function open() { - this.$node.addClass(this.classes.open); - }, - close: function close() { - this.$node.removeClass(this.classes.open); - this._removeCursor(); - }, - setLanguageDirection: function setLanguageDirection(dir) { - this.$node.attr("dir", dir); - }, - selectableRelativeToCursor: function selectableRelativeToCursor(delta) { - var $selectables, $oldCursor, oldIndex, newIndex; - $oldCursor = this.getActiveSelectable(); - $selectables = this._getSelectables(); - oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; - newIndex = oldIndex + delta; - newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; - newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; - return newIndex === -1 ? null : $selectables.eq(newIndex); - }, - setCursor: function setCursor($selectable) { - this._removeCursor(); - if ($selectable = $selectable && $selectable.first()) { - $selectable.addClass(this.classes.cursor); - this._ensureVisible($selectable); - } - }, - getSelectableData: function getSelectableData($el) { - return $el && $el.length ? Dataset.extractData($el) : null; - }, - getActiveSelectable: function getActiveSelectable() { - var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); - return $selectable.length ? $selectable : null; - }, - getTopSelectable: function getTopSelectable() { - var $selectable = this._getSelectables().first(); - return $selectable.length ? $selectable : null; - }, - update: function update(query) { - var isValidUpdate = query !== this.query; - if (isValidUpdate) { - this.query = query; - _.each(this.datasets, updateDataset); - } - return isValidUpdate; - function updateDataset(dataset) { - dataset.update(query); - } - }, - empty: function empty() { - _.each(this.datasets, clearDataset); - this.query = null; - this.$node.addClass(this.classes.empty); - function clearDataset(dataset) { - dataset.clear(); - } - }, - destroy: function destroy() { - this.$node.off(".tt"); - this.$node = $("
    "); - _.each(this.datasets, destroyDataset); - function destroyDataset(dataset) { - dataset.destroy(); - } - } - }); - return Menu; - }(); - var DefaultMenu = function() { - "use strict"; - var s = Menu.prototype; - function DefaultMenu() { - Menu.apply(this, [].slice.call(arguments, 0)); - } - _.mixin(DefaultMenu.prototype, Menu.prototype, { - open: function open() { - !this._allDatasetsEmpty() && this._show(); - return s.open.apply(this, [].slice.call(arguments, 0)); - }, - close: function close() { - this._hide(); - return s.close.apply(this, [].slice.call(arguments, 0)); - }, - _onRendered: function onRendered() { - if (this._allDatasetsEmpty()) { - this._hide(); - } else { - this.isOpen() && this._show(); - } - return s._onRendered.apply(this, [].slice.call(arguments, 0)); - }, - _onCleared: function onCleared() { - if (this._allDatasetsEmpty()) { - this._hide(); - } else { - this.isOpen() && this._show(); - } - return s._onCleared.apply(this, [].slice.call(arguments, 0)); - }, - setLanguageDirection: function setLanguageDirection(dir) { - this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); - return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); - }, - _hide: function hide() { - this.$node.hide(); - }, - _show: function show() { - this.$node.css("display", "block"); - } - }); - return DefaultMenu; - }(); - var Typeahead = function() { - "use strict"; - function Typeahead(o, www) { - var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; - o = o || {}; - if (!o.input) { - $.error("missing input"); - } - if (!o.menu) { - $.error("missing menu"); - } - if (!o.eventBus) { - $.error("missing event bus"); - } - www.mixin(this); - this.eventBus = o.eventBus; - this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; - this.input = o.input; - this.menu = o.menu; - this.enabled = true; - this.active = false; - this.input.hasFocus() && this.activate(); - this.dir = this.input.getLangDir(); - this._hacks(); - this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); - onFocused = c(this, "activate", "open", "_onFocused"); - onBlurred = c(this, "deactivate", "_onBlurred"); - onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); - onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); - onEscKeyed = c(this, "isActive", "_onEscKeyed"); - onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); - onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); - onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); - onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); - onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); - onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); - this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); - } - _.mixin(Typeahead.prototype, { - _hacks: function hacks() { - var $input, $menu; - $input = this.input.$input || $("
    "); - $menu = this.menu.$node || $("
    "); - $input.on("blur.tt", function($e) { - var active, isActive, hasActive; - active = document.activeElement; - isActive = $menu.is(active); - hasActive = $menu.has(active).length > 0; - if (_.isMsie() && (isActive || hasActive)) { - $e.preventDefault(); - $e.stopImmediatePropagation(); - _.defer(function() { - $input.focus(); - }); - } - }); - $menu.on("mousedown.tt", function($e) { - $e.preventDefault(); - }); - }, - _onSelectableClicked: function onSelectableClicked(type, $el) { - this.select($el); - }, - _onDatasetCleared: function onDatasetCleared() { - this._updateHint(); - }, - _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) { - this._updateHint(); - this.eventBus.trigger("render", suggestions, async, dataset); - }, - _onAsyncRequested: function onAsyncRequested(type, dataset, query) { - this.eventBus.trigger("asyncrequest", query, dataset); - }, - _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { - this.eventBus.trigger("asynccancel", query, dataset); - }, - _onAsyncReceived: function onAsyncReceived(type, dataset, query) { - this.eventBus.trigger("asyncreceive", query, dataset); - }, - _onFocused: function onFocused() { - this._minLengthMet() && this.menu.update(this.input.getQuery()); - }, - _onBlurred: function onBlurred() { - if (this.input.hasQueryChangedSinceLastFocus()) { - this.eventBus.trigger("change", this.input.getQuery()); - } - }, - _onEnterKeyed: function onEnterKeyed(type, $e) { - var $selectable; - if ($selectable = this.menu.getActiveSelectable()) { - this.select($selectable) && $e.preventDefault(); - } - }, - _onTabKeyed: function onTabKeyed(type, $e) { - var $selectable; - if ($selectable = this.menu.getActiveSelectable()) { - this.select($selectable) && $e.preventDefault(); - } else if ($selectable = this.menu.getTopSelectable()) { - this.autocomplete($selectable) && $e.preventDefault(); - } - }, - _onEscKeyed: function onEscKeyed() { - this.close(); - }, - _onUpKeyed: function onUpKeyed() { - this.moveCursor(-1); - }, - _onDownKeyed: function onDownKeyed() { - this.moveCursor(+1); - }, - _onLeftKeyed: function onLeftKeyed() { - if (this.dir === "rtl" && this.input.isCursorAtEnd()) { - this.autocomplete(this.menu.getTopSelectable()); - } - }, - _onRightKeyed: function onRightKeyed() { - if (this.dir === "ltr" && this.input.isCursorAtEnd()) { - this.autocomplete(this.menu.getTopSelectable()); - } - }, - _onQueryChanged: function onQueryChanged(e, query) { - this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); - }, - _onWhitespaceChanged: function onWhitespaceChanged() { - this._updateHint(); - }, - _onLangDirChanged: function onLangDirChanged(e, dir) { - if (this.dir !== dir) { - this.dir = dir; - this.menu.setLanguageDirection(dir); - } - }, - _openIfActive: function openIfActive() { - this.isActive() && this.open(); - }, - _minLengthMet: function minLengthMet(query) { - query = _.isString(query) ? query : this.input.getQuery() || ""; - return query.length >= this.minLength; - }, - _updateHint: function updateHint() { - var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; - $selectable = this.menu.getTopSelectable(); - data = this.menu.getSelectableData($selectable); - val = this.input.getInputValue(); - if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { - query = Input.normalizeQuery(val); - escapedQuery = _.escapeRegExChars(query); - frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); - match = frontMatchRegEx.exec(data.val); - match && this.input.setHint(val + match[1]); - } else { - this.input.clearHint(); - } - }, - isEnabled: function isEnabled() { - return this.enabled; - }, - enable: function enable() { - this.enabled = true; - }, - disable: function disable() { - this.enabled = false; - }, - isActive: function isActive() { - return this.active; - }, - activate: function activate() { - if (this.isActive()) { - return true; - } else if (!this.isEnabled() || this.eventBus.before("active")) { - return false; - } else { - this.active = true; - this.eventBus.trigger("active"); - return true; - } - }, - deactivate: function deactivate() { - if (!this.isActive()) { - return true; - } else if (this.eventBus.before("idle")) { - return false; - } else { - this.active = false; - this.close(); - this.eventBus.trigger("idle"); - return true; - } - }, - isOpen: function isOpen() { - return this.menu.isOpen(); - }, - open: function open() { - if (!this.isOpen() && !this.eventBus.before("open")) { - this.menu.open(); - this._updateHint(); - this.eventBus.trigger("open"); - } - return this.isOpen(); - }, - close: function close() { - if (this.isOpen() && !this.eventBus.before("close")) { - this.menu.close(); - this.input.clearHint(); - this.input.resetInputValue(); - this.eventBus.trigger("close"); - } - return !this.isOpen(); - }, - setVal: function setVal(val) { - this.input.setQuery(_.toStr(val)); - }, - getVal: function getVal() { - return this.input.getQuery(); - }, - select: function select($selectable) { - var data = this.menu.getSelectableData($selectable); - if (data && !this.eventBus.before("select", data.obj)) { - this.input.setQuery(data.val, true); - this.eventBus.trigger("select", data.obj); - this.close(); - return true; - } - return false; - }, - autocomplete: function autocomplete($selectable) { - var query, data, isValid; - query = this.input.getQuery(); - data = this.menu.getSelectableData($selectable); - isValid = data && query !== data.val; - if (isValid && !this.eventBus.before("autocomplete", data.obj)) { - this.input.setQuery(data.val); - this.eventBus.trigger("autocomplete", data.obj); - return true; - } - return false; - }, - moveCursor: function moveCursor(delta) { - var query, $candidate, data, payload, cancelMove; - query = this.input.getQuery(); - $candidate = this.menu.selectableRelativeToCursor(delta); - data = this.menu.getSelectableData($candidate); - payload = data ? data.obj : null; - cancelMove = this._minLengthMet() && this.menu.update(query); - if (!cancelMove && !this.eventBus.before("cursorchange", payload)) { - this.menu.setCursor($candidate); - if (data) { - this.input.setInputValue(data.val); - } else { - this.input.resetInputValue(); - this._updateHint(); - } - this.eventBus.trigger("cursorchange", payload); - return true; - } - return false; - }, - destroy: function destroy() { - this.input.destroy(); - this.menu.destroy(); - } - }); - return Typeahead; - function c(ctx) { - var methods = [].slice.call(arguments, 1); - return function() { - var args = [].slice.call(arguments); - _.each(methods, function(method) { - return ctx[method].apply(ctx, args); - }); - }; + function destroyDomStructure($node) { + var $input = $node.find(".tt-query"); + utils.each($input.data("ttAttrs"), function(key, val) { + utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node); + $node.remove(); } }(); (function() { - "use strict"; - var old, keys, methods; - old = $.fn.typeahead; - keys = { - www: "tt-www", - attrs: "tt-attrs", - typeahead: "tt-typeahead" - }; + var cache = {}, viewKey = "ttView", methods; methods = { - initialize: function initialize(o, datasets) { - var www; - datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); - o = o || {}; - www = WWW(o.classNames); - return this.each(attach); - function attach() { - var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor; - _.each(datasets, function(d) { - d.highlight = !!o.highlight; - }); - $input = $(this); - $wrapper = $(www.html.wrapper); - $hint = $elOrNull(o.hint); - $menu = $elOrNull(o.menu); - defaultHint = o.hint !== false && !$hint; - defaultMenu = o.menu !== false && !$menu; - defaultHint && ($hint = buildHintFromInput($input, www)); - defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); - $hint && $hint.val(""); - $input = prepInput($input, www); - if (defaultHint || defaultMenu) { - $wrapper.css(www.css.wrapper); - $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); - $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); + initialize: function(datasetDefs) { + var datasets; + datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ]; + if (datasetDefs.length === 0) { + $.error("no datasets provided"); + } + datasets = utils.map(datasetDefs, function(o) { + var dataset = cache[o.name] ? cache[o.name] : new Dataset(o); + if (o.name) { + cache[o.name] = dataset; } - MenuConstructor = defaultMenu ? DefaultMenu : Menu; - eventBus = new EventBus({ + return dataset; + }); + return this.each(initialize); + function initialize() { + var $input = $(this), deferreds, eventBus = new EventBus({ el: $input }); - input = new Input({ - hint: $hint, - input: $input - }, www); - menu = new MenuConstructor({ - node: $menu, + deferreds = utils.map(datasets, function(dataset) { + return dataset.initialize(); + }); + $input.data(viewKey, new TypeaheadView({ + input: $input, + eventBus: eventBus = new EventBus({ + el: $input + }), datasets: datasets - }, www); - typeahead = new Typeahead({ - input: input, - menu: menu, - eventBus: eventBus, - minLength: o.minLength - }, www); - $input.data(keys.www, www); - $input.data(keys.typeahead, typeahead); + })); + $.when.apply($, deferreds).always(function() { + utils.defer(function() { + eventBus.trigger("initialized"); + }); + }); } }, - isEnabled: function isEnabled() { - var enabled; - ttEach(this.first(), function(t) { - enabled = t.isEnabled(); - }); - return enabled; - }, - enable: function enable() { - ttEach(this, function(t) { - t.enable(); - }); - return this; - }, - disable: function disable() { - ttEach(this, function(t) { - t.disable(); - }); - return this; - }, - isActive: function isActive() { - var active; - ttEach(this.first(), function(t) { - active = t.isActive(); - }); - return active; - }, - activate: function activate() { - ttEach(this, function(t) { - t.activate(); - }); - return this; - }, - deactivate: function deactivate() { - ttEach(this, function(t) { - t.deactivate(); - }); - return this; - }, - isOpen: function isOpen() { - var open; - ttEach(this.first(), function(t) { - open = t.isOpen(); - }); - return open; - }, - open: function open() { - ttEach(this, function(t) { - t.open(); - }); - return this; - }, - close: function close() { - ttEach(this, function(t) { - t.close(); - }); - return this; - }, - select: function select(el) { - var success = false, $el = $(el); - ttEach(this.first(), function(t) { - success = t.select($el); - }); - return success; - }, - autocomplete: function autocomplete(el) { - var success = false, $el = $(el); - ttEach(this.first(), function(t) { - success = t.autocomplete($el); - }); - return success; - }, - moveCursor: function moveCursoe(delta) { - var success = false; - ttEach(this.first(), function(t) { - success = t.moveCursor(delta); - }); - return success; - }, - val: function val(newVal) { - var query; - if (!arguments.length) { - ttEach(this.first(), function(t) { - query = t.getVal(); - }); - return query; - } else { - ttEach(this, function(t) { - t.setVal(newVal); - }); - return this; + destroy: function() { + return this.each(destroy); + function destroy() { + var $this = $(this), view = $this.data(viewKey); + if (view) { + view.destroy(); + $this.removeData(viewKey); + } } }, - destroy: function destroy() { - ttEach(this, function(typeahead, $input) { - revert($input); - typeahead.destroy(); - }); - return this; + setQuery: function(query) { + return this.each(setQuery); + function setQuery() { + var view = $(this).data(viewKey); + view && view.setQuery(query); + } } }; - $.fn.typeahead = function(method) { + jQuery.fn.typeahead = function(method) { if (methods[method]) { return methods[method].apply(this, [].slice.call(arguments, 1)); } else { return methods.initialize.apply(this, arguments); } }; - $.fn.typeahead.noConflict = function noConflict() { - $.fn.typeahead = old; - return this; - }; - function ttEach($els, fn) { - $els.each(function() { - var $input = $(this), typeahead; - (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); - }); - } - function buildHintFromInput($input, www) { - return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({ - autocomplete: "off", - spellcheck: "false", - tabindex: -1 - }); - } - function prepInput($input, www) { - $input.data(keys.attrs, { - dir: $input.attr("dir"), - autocomplete: $input.attr("autocomplete"), - spellcheck: $input.attr("spellcheck"), - style: $input.attr("style") - }); - $input.addClass(www.classes.input).attr({ - autocomplete: "off", - spellcheck: false - }); - try { - !$input.attr("dir") && $input.attr("dir", "auto"); - } catch (e) {} - return $input; - } - function getBackgroundStyles($el) { - return { - backgroundAttachment: $el.css("background-attachment"), - backgroundClip: $el.css("background-clip"), - backgroundColor: $el.css("background-color"), - backgroundImage: $el.css("background-image"), - backgroundOrigin: $el.css("background-origin"), - backgroundPosition: $el.css("background-position"), - backgroundRepeat: $el.css("background-repeat"), - backgroundSize: $el.css("background-size") - }; - } - function revert($input) { - var www, $wrapper; - www = $input.data(keys.www); - $wrapper = $input.parent().filter(www.selectors.wrapper); - _.each($input.data(keys.attrs), function(val, key) { - _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); - }); - $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); - if ($wrapper.length) { - $input.detach().insertAfter($wrapper); - $wrapper.remove(); - } - } - function $elOrNull(obj) { - var isValid, $el; - isValid = _.isJQuery(obj) || _.isElement(obj); - $el = isValid ? $(obj).first() : []; - return $el.length ? $el : null; - } })(); -}); \ No newline at end of file +})(window.jQuery); diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index 5792eb96..af0518d0 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -669,7 +669,7 @@ Metamaps.Create = { { minLength: 2, }, - { + [{ name: 'topic_autocomplete', limit: 8, template: $('#topicAutocompleteTemplate').html(), @@ -677,7 +677,7 @@ Metamaps.Create = { url: '/topics/autocomplete_topic?term=%QUERY' }, engine: Hogan - } + }] ); // tell the autocomplete to submit the form with the topic you clicked on if you pick from the autocomplete @@ -731,7 +731,7 @@ Metamaps.Create = { { minLength: 2, }, - { + [{ name: 'synapse_autocomplete', template: "
    {{label}}
    ", remote: { @@ -751,7 +751,7 @@ Metamaps.Create = { }, engine: Hogan, header: "

    Existing synapses

    " - } + }] ); $('#synapse_desc').bind('typeahead:selected', function (event, datum, dataset) { From d895bca1a46e043d708840ef2bc332d30ef81bfd Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 1 Oct 2015 12:33:38 +0800 Subject: [PATCH 028/122] code tweaks to searchsynapses --- app/controllers/main_controller.rb | 46 ++++++++++++------------------ 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 3d2a341f..1e8b0c9c 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -68,25 +68,20 @@ class MainController < ApplicationController else search = term.downcase + '%' - if !user - @topics = Topic.where('LOWER("name") like ?', search).where('metacode_id = ?', filterByMetacode.id).order('"name"') - @topics2 = Topic.where('LOWER("name") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"') - @topics3 = Topic.where('LOWER("desc") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"') - @topics4 = Topic.where('LOWER("link") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"') - @topics = @topics + (@topics2 - @topics) - @topics = @topics + (@topics3 - @topics) - @topics = @topics + (@topics4 - @topics) - - elsif user - @topics = Topic.where('LOWER("name") like ?', search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"') - @topics2 = Topic.where('LOWER("name") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"') - @topics3 = Topic.where('LOWER("desc") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"') - @topics4 = Topic.where('LOWER("link") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"') - @topics = @topics + (@topics2 - @topics) - @topics = @topics + (@topics3 - @topics) - @topics = @topics + (@topics4 - @topics) - + if user + @topics = Set.new(Topic.where('LOWER("name") like ?', search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"')) + @topics2 = Set.new(Topic.where('LOWER("name") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"')) + @topics3 = Set.new(Topic.where('LOWER("desc") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"')) + @topics4 = Set.new(Topic.where('LOWER("link") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"')) + else + @topics = Set.new(Topic.where('LOWER("name") like ?', search).where('metacode_id = ?', filterByMetacode.id).order('"name"')) + @topics2 = Set.new(Topic.where('LOWER("name") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"')) + @topics3 = Set.new(Topic.where('LOWER("desc") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"')) + @topics4 = Set.new(Topic.where('LOWER("link") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"')) end + + #get unique elements only through the magic of Sets + @topics = (@topics + @topics2 + @topics3 + @topics4).to_a end elsif desc search = '%' + term.downcase + '%' @@ -211,23 +206,20 @@ class MainController < ApplicationController #limit to 5 results @synapses = @synapses.slice(0,5) - - render json: autocomplete_synapse_generic_json(@synapses) - elsif topic1id && !topic1id.empty? @one = Synapse.where('node1_id = ? AND node2_id = ?', topic1id, topic2id) @two = Synapse.where('node2_id = ? AND node1_id = ?', topic1id, topic2id) @synapses = @one + @two - @synapses.sort! {|s1,s2| s1.desc <=> s2.desc } + @synapses.sort! {|s1,s2| s1.desc <=> s2.desc }.to_a - #read this next line as 'delete a synapse if its private and you're either 1. logged out or 2. logged in but not the synapse creator - @synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } - - render json: autocomplete_synapse_array_json(@synapses) + #permissions + @synapses.delete_if {|s| s.permission == "private" && !authenticated? } + @synapses.delete_if {|s| s.permission == "private" && authenticated? && @current.id != s.user_id } else @synapses = [] - render json: autocomplete_synapse_array_json(@synapses) end + + render json: utocomplete_synapse_array_json(@synapses) end end From 14eeb0cbe63a6407090b16717e0a4eff1f1b93d2 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Thu, 1 Oct 2015 14:55:26 +0800 Subject: [PATCH 029/122] upgrade typeahead to 1.10 and new syntax --- .../javascripts/lib/typeahead.bundle.js | 2451 +++++++++++++++++ app/assets/javascripts/lib/typeahead.js | 1165 -------- app/assets/javascripts/src/Metamaps.js | 52 +- 3 files changed, 2481 insertions(+), 1187 deletions(-) create mode 100644 app/assets/javascripts/lib/typeahead.bundle.js delete mode 100644 app/assets/javascripts/lib/typeahead.js diff --git a/app/assets/javascripts/lib/typeahead.bundle.js b/app/assets/javascripts/lib/typeahead.bundle.js new file mode 100644 index 00000000..bb0c8aed --- /dev/null +++ b/app/assets/javascripts/lib/typeahead.bundle.js @@ -0,0 +1,2451 @@ +/*! + * typeahead.js 0.11.1 + * https://github.com/twitter/typeahead.js + * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT + */ + +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define("bloodhound", [ "jquery" ], function(a0) { + return root["Bloodhound"] = factory(a0); + }); + } else if (typeof exports === "object") { + module.exports = factory(require("jquery")); + } else { + root["Bloodhound"] = factory(jQuery); + } +})(this, function($) { + var _ = function() { + "use strict"; + return { + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isJQuery: function(obj) { + return obj instanceof $; + }, + toStr: function toStr(s) { + return _.isUndefined(s) || s === null ? "" : s + ""; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); + } + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + identity: function(x) { + return x; + }, + clone: function(obj) { + return $.extend(true, {}, obj); + }, + getIdGenerator: function() { + var counter = 0; + return function() { + return counter++; + }; + }, + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + stringify: function(val) { + return _.isString(val) ? val : JSON.stringify(val); + }, + noop: function() {} + }; + }(); + var VERSION = "0.11.1"; + var tokenizers = function() { + "use strict"; + return { + nonword: nonword, + whitespace: whitespace, + obj: { + nonword: getObjTokenizer(nonword), + whitespace: getObjTokenizer(whitespace) + } + }; + function whitespace(str) { + str = _.toStr(str); + return str ? str.split(/\s+/) : []; + } + function nonword(str) { + str = _.toStr(str); + return str ? str.split(/\W+/) : []; + } + function getObjTokenizer(tokenizer) { + return function setKey(keys) { + keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0); + return function tokenize(o) { + var tokens = []; + _.each(keys, function(k) { + tokens = tokens.concat(tokenizer(_.toStr(o[k]))); + }); + return tokens; + }; + }; + } + }(); + var LruCache = function() { + "use strict"; + function LruCache(maxSize) { + this.maxSize = _.isNumber(maxSize) ? maxSize : 100; + this.reset(); + if (this.maxSize <= 0) { + this.set = this.get = $.noop; + } + } + _.mixin(LruCache.prototype, { + set: function set(key, val) { + var tailItem = this.list.tail, node; + if (this.size >= this.maxSize) { + this.list.remove(tailItem); + delete this.hash[tailItem.key]; + this.size--; + } + if (node = this.hash[key]) { + node.val = val; + this.list.moveToFront(node); + } else { + node = new Node(key, val); + this.list.add(node); + this.hash[key] = node; + this.size++; + } + }, + get: function get(key) { + var node = this.hash[key]; + if (node) { + this.list.moveToFront(node); + return node.val; + } + }, + reset: function reset() { + this.size = 0; + this.hash = {}; + this.list = new List(); + } + }); + function List() { + this.head = this.tail = null; + } + _.mixin(List.prototype, { + add: function add(node) { + if (this.head) { + node.next = this.head; + this.head.prev = node; + } + this.head = node; + this.tail = this.tail || node; + }, + remove: function remove(node) { + node.prev ? node.prev.next = node.next : this.head = node.next; + node.next ? node.next.prev = node.prev : this.tail = node.prev; + }, + moveToFront: function(node) { + this.remove(node); + this.add(node); + } + }); + function Node(key, val) { + this.key = key; + this.val = val; + this.prev = this.next = null; + } + return LruCache; + }(); + var PersistentStorage = function() { + "use strict"; + var LOCAL_STORAGE; + try { + LOCAL_STORAGE = window.localStorage; + LOCAL_STORAGE.setItem("~~~", "!"); + LOCAL_STORAGE.removeItem("~~~"); + } catch (err) { + LOCAL_STORAGE = null; + } + function PersistentStorage(namespace, override) { + this.prefix = [ "__", namespace, "__" ].join(""); + this.ttlKey = "__ttl__"; + this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix)); + this.ls = override || LOCAL_STORAGE; + !this.ls && this._noop(); + } + _.mixin(PersistentStorage.prototype, { + _prefix: function(key) { + return this.prefix + key; + }, + _ttlKey: function(key) { + return this._prefix(key) + this.ttlKey; + }, + _noop: function() { + this.get = this.set = this.remove = this.clear = this.isExpired = _.noop; + }, + _safeSet: function(key, val) { + try { + this.ls.setItem(key, val); + } catch (err) { + if (err.name === "QuotaExceededError") { + this.clear(); + this._noop(); + } + } + }, + get: function(key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(this.ls.getItem(this._prefix(key))); + }, + set: function(key, val, ttl) { + if (_.isNumber(ttl)) { + this._safeSet(this._ttlKey(key), encode(now() + ttl)); + } else { + this.ls.removeItem(this._ttlKey(key)); + } + return this._safeSet(this._prefix(key), encode(val)); + }, + remove: function(key) { + this.ls.removeItem(this._ttlKey(key)); + this.ls.removeItem(this._prefix(key)); + return this; + }, + clear: function() { + var i, keys = gatherMatchingKeys(this.keyMatcher); + for (i = keys.length; i--; ) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function(key) { + var ttl = decode(this.ls.getItem(this._ttlKey(key))); + return _.isNumber(ttl) && now() > ttl ? true : false; + } + }); + return PersistentStorage; + function now() { + return new Date().getTime(); + } + function encode(val) { + return JSON.stringify(_.isUndefined(val) ? null : val); + } + function decode(val) { + return $.parseJSON(val); + } + function gatherMatchingKeys(keyMatcher) { + var i, key, keys = [], len = LOCAL_STORAGE.length; + for (i = 0; i < len; i++) { + if ((key = LOCAL_STORAGE.key(i)).match(keyMatcher)) { + keys.push(key.replace(keyMatcher, "")); + } + } + return keys; + } + }(); + var Transport = function() { + "use strict"; + var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10); + function Transport(o) { + o = o || {}; + this.cancelled = false; + this.lastReq = null; + this._send = o.transport; + this._get = o.limiter ? o.limiter(this._get) : this._get; + this._cache = o.cache === false ? new LruCache(0) : sharedCache; + } + Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { + maxPendingRequests = num; + }; + Transport.resetCache = function resetCache() { + sharedCache.reset(); + }; + _.mixin(Transport.prototype, { + _fingerprint: function fingerprint(o) { + o = o || {}; + return o.url + o.type + $.param(o.data || {}); + }, + _get: function(o, cb) { + var that = this, fingerprint, jqXhr; + fingerprint = this._fingerprint(o); + if (this.cancelled || fingerprint !== this.lastReq) { + return; + } + if (jqXhr = pendingRequests[fingerprint]) { + jqXhr.done(done).fail(fail); + } else if (pendingRequestsCount < maxPendingRequests) { + pendingRequestsCount++; + pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always); + } else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + function done(resp) { + cb(null, resp); + that._cache.set(fingerprint, resp); + } + function fail() { + cb(true); + } + function always() { + pendingRequestsCount--; + delete pendingRequests[fingerprint]; + if (that.onDeckRequestArgs) { + that._get.apply(that, that.onDeckRequestArgs); + that.onDeckRequestArgs = null; + } + } + }, + get: function(o, cb) { + var resp, fingerprint; + cb = cb || $.noop; + o = _.isString(o) ? { + url: o + } : o || {}; + fingerprint = this._fingerprint(o); + this.cancelled = false; + this.lastReq = fingerprint; + if (resp = this._cache.get(fingerprint)) { + cb(null, resp); + } else { + this._get(o, cb); + } + }, + cancel: function() { + this.cancelled = true; + } + }); + return Transport; + }(); + var SearchIndex = window.SearchIndex = function() { + "use strict"; + var CHILDREN = "c", IDS = "i"; + function SearchIndex(o) { + o = o || {}; + if (!o.datumTokenizer || !o.queryTokenizer) { + $.error("datumTokenizer and queryTokenizer are both required"); + } + this.identify = o.identify || _.stringify; + this.datumTokenizer = o.datumTokenizer; + this.queryTokenizer = o.queryTokenizer; + this.reset(); + } + _.mixin(SearchIndex.prototype, { + bootstrap: function bootstrap(o) { + this.datums = o.datums; + this.trie = o.trie; + }, + add: function(data) { + var that = this; + data = _.isArray(data) ? data : [ data ]; + _.each(data, function(datum) { + var id, tokens; + that.datums[id = that.identify(datum)] = datum; + tokens = normalizeTokens(that.datumTokenizer(datum)); + _.each(tokens, function(token) { + var node, chars, ch; + node = that.trie; + chars = token.split(""); + while (ch = chars.shift()) { + node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode()); + node[IDS].push(id); + } + }); + }); + }, + get: function get(ids) { + var that = this; + return _.map(ids, function(id) { + return that.datums[id]; + }); + }, + search: function search(query) { + var that = this, tokens, matches; + tokens = normalizeTokens(this.queryTokenizer(query)); + _.each(tokens, function(token) { + var node, chars, ch, ids; + if (matches && matches.length === 0) { + return false; + } + node = that.trie; + chars = token.split(""); + while (node && (ch = chars.shift())) { + node = node[CHILDREN][ch]; + } + if (node && chars.length === 0) { + ids = node[IDS].slice(0); + matches = matches ? getIntersection(matches, ids) : ids; + } else { + matches = []; + return false; + } + }); + return matches ? _.map(unique(matches), function(id) { + return that.datums[id]; + }) : []; + }, + all: function all() { + var values = []; + for (var key in this.datums) { + values.push(this.datums[key]); + } + return values; + }, + reset: function reset() { + this.datums = {}; + this.trie = newNode(); + }, + serialize: function serialize() { + return { + datums: this.datums, + trie: this.trie + }; + } + }); + return SearchIndex; + function normalizeTokens(tokens) { + tokens = _.filter(tokens, function(token) { + return !!token; + }); + tokens = _.map(tokens, function(token) { + return token.toLowerCase(); + }); + return tokens; + } + function newNode() { + var node = {}; + node[IDS] = []; + node[CHILDREN] = {}; + return node; + } + function unique(array) { + var seen = {}, uniques = []; + for (var i = 0, len = array.length; i < len; i++) { + if (!seen[array[i]]) { + seen[array[i]] = true; + uniques.push(array[i]); + } + } + return uniques; + } + function getIntersection(arrayA, arrayB) { + var ai = 0, bi = 0, intersection = []; + arrayA = arrayA.sort(); + arrayB = arrayB.sort(); + var lenArrayA = arrayA.length, lenArrayB = arrayB.length; + while (ai < lenArrayA && bi < lenArrayB) { + if (arrayA[ai] < arrayB[bi]) { + ai++; + } else if (arrayA[ai] > arrayB[bi]) { + bi++; + } else { + intersection.push(arrayA[ai]); + ai++; + bi++; + } + } + return intersection; + } + }(); + var Prefetch = function() { + "use strict"; + var keys; + keys = { + data: "data", + protocol: "protocol", + thumbprint: "thumbprint" + }; + function Prefetch(o) { + this.url = o.url; + this.ttl = o.ttl; + this.cache = o.cache; + this.prepare = o.prepare; + this.transform = o.transform; + this.transport = o.transport; + this.thumbprint = o.thumbprint; + this.storage = new PersistentStorage(o.cacheKey); + } + _.mixin(Prefetch.prototype, { + _settings: function settings() { + return { + url: this.url, + type: "GET", + dataType: "json" + }; + }, + store: function store(data) { + if (!this.cache) { + return; + } + this.storage.set(keys.data, data, this.ttl); + this.storage.set(keys.protocol, location.protocol, this.ttl); + this.storage.set(keys.thumbprint, this.thumbprint, this.ttl); + }, + fromCache: function fromCache() { + var stored = {}, isExpired; + if (!this.cache) { + return null; + } + stored.data = this.storage.get(keys.data); + stored.protocol = this.storage.get(keys.protocol); + stored.thumbprint = this.storage.get(keys.thumbprint); + isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol; + return stored.data && !isExpired ? stored.data : null; + }, + fromNetwork: function(cb) { + var that = this, settings; + if (!cb) { + return; + } + settings = this.prepare(this._settings()); + this.transport(settings).fail(onError).done(onResponse); + function onError() { + cb(true); + } + function onResponse(resp) { + cb(null, that.transform(resp)); + } + }, + clear: function clear() { + this.storage.clear(); + return this; + } + }); + return Prefetch; + }(); + var Remote = function() { + "use strict"; + function Remote(o) { + this.url = o.url; + this.prepare = o.prepare; + this.transform = o.transform; + this.transport = new Transport({ + cache: o.cache, + limiter: o.limiter, + transport: o.transport + }); + } + _.mixin(Remote.prototype, { + _settings: function settings() { + return { + url: this.url, + type: "GET", + dataType: "json" + }; + }, + get: function get(query, cb) { + var that = this, settings; + if (!cb) { + return; + } + query = query || ""; + settings = this.prepare(query, this._settings()); + return this.transport.get(settings, onResponse); + function onResponse(err, resp) { + err ? cb([]) : cb(that.transform(resp)); + } + }, + cancelLastRequest: function cancelLastRequest() { + this.transport.cancel(); + } + }); + return Remote; + }(); + var oParser = function() { + "use strict"; + return function parse(o) { + var defaults, sorter; + defaults = { + initialize: true, + identify: _.stringify, + datumTokenizer: null, + queryTokenizer: null, + sufficient: 5, + sorter: null, + local: [], + prefetch: null, + remote: null + }; + o = _.mixin(defaults, o || {}); + !o.datumTokenizer && $.error("datumTokenizer is required"); + !o.queryTokenizer && $.error("queryTokenizer is required"); + sorter = o.sorter; + o.sorter = sorter ? function(x) { + return x.sort(sorter); + } : _.identity; + o.local = _.isFunction(o.local) ? o.local() : o.local; + o.prefetch = parsePrefetch(o.prefetch); + o.remote = parseRemote(o.remote); + return o; + }; + function parsePrefetch(o) { + var defaults; + if (!o) { + return null; + } + defaults = { + url: null, + ttl: 24 * 60 * 60 * 1e3, + cache: true, + cacheKey: null, + thumbprint: "", + prepare: _.identity, + transform: _.identity, + transport: null + }; + o = _.isString(o) ? { + url: o + } : o; + o = _.mixin(defaults, o); + !o.url && $.error("prefetch requires url to be set"); + o.transform = o.filter || o.transform; + o.cacheKey = o.cacheKey || o.url; + o.thumbprint = VERSION + o.thumbprint; + o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; + return o; + } + function parseRemote(o) { + var defaults; + if (!o) { + return; + } + defaults = { + url: null, + cache: true, + prepare: null, + replace: null, + wildcard: null, + limiter: null, + rateLimitBy: "debounce", + rateLimitWait: 300, + transform: _.identity, + transport: null + }; + o = _.isString(o) ? { + url: o + } : o; + o = _.mixin(defaults, o); + !o.url && $.error("remote requires url to be set"); + o.transform = o.filter || o.transform; + o.prepare = toRemotePrepare(o); + o.limiter = toLimiter(o); + o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; + delete o.replace; + delete o.wildcard; + delete o.rateLimitBy; + delete o.rateLimitWait; + return o; + } + function toRemotePrepare(o) { + var prepare, replace, wildcard; + prepare = o.prepare; + replace = o.replace; + wildcard = o.wildcard; + if (prepare) { + return prepare; + } + if (replace) { + prepare = prepareByReplace; + } else if (o.wildcard) { + prepare = prepareByWildcard; + } else { + prepare = idenityPrepare; + } + return prepare; + function prepareByReplace(query, settings) { + settings.url = replace(settings.url, query); + return settings; + } + function prepareByWildcard(query, settings) { + settings.url = settings.url.replace(wildcard, encodeURIComponent(query)); + return settings; + } + function idenityPrepare(query, settings) { + return settings; + } + } + function toLimiter(o) { + var limiter, method, wait; + limiter = o.limiter; + method = o.rateLimitBy; + wait = o.rateLimitWait; + if (!limiter) { + limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait); + } + return limiter; + function debounce(wait) { + return function debounce(fn) { + return _.debounce(fn, wait); + }; + } + function throttle(wait) { + return function throttle(fn) { + return _.throttle(fn, wait); + }; + } + } + function callbackToDeferred(fn) { + return function wrapper(o) { + var deferred = $.Deferred(); + fn(o, onSuccess, onError); + return deferred; + function onSuccess(resp) { + _.defer(function() { + deferred.resolve(resp); + }); + } + function onError(err) { + _.defer(function() { + deferred.reject(err); + }); + } + }; + } + }(); + var Bloodhound = function() { + "use strict"; + var old; + old = window && window.Bloodhound; + function Bloodhound(o) { + o = oParser(o); + this.sorter = o.sorter; + this.identify = o.identify; + this.sufficient = o.sufficient; + this.local = o.local; + this.remote = o.remote ? new Remote(o.remote) : null; + this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null; + this.index = new SearchIndex({ + identify: this.identify, + datumTokenizer: o.datumTokenizer, + queryTokenizer: o.queryTokenizer + }); + o.initialize !== false && this.initialize(); + } + Bloodhound.noConflict = function noConflict() { + window && (window.Bloodhound = old); + return Bloodhound; + }; + Bloodhound.tokenizers = tokenizers; + _.mixin(Bloodhound.prototype, { + __ttAdapter: function ttAdapter() { + var that = this; + return this.remote ? withAsync : withoutAsync; + function withAsync(query, sync, async) { + return that.search(query, sync, async); + } + function withoutAsync(query, sync) { + return that.search(query, sync); + } + }, + _loadPrefetch: function loadPrefetch() { + var that = this, deferred, serialized; + deferred = $.Deferred(); + if (!this.prefetch) { + deferred.resolve(); + } else if (serialized = this.prefetch.fromCache()) { + this.index.bootstrap(serialized); + deferred.resolve(); + } else { + this.prefetch.fromNetwork(done); + } + return deferred.promise(); + function done(err, data) { + if (err) { + return deferred.reject(); + } + that.add(data); + that.prefetch.store(that.index.serialize()); + deferred.resolve(); + } + }, + _initialize: function initialize() { + var that = this, deferred; + this.clear(); + (this.initPromise = this._loadPrefetch()).done(addLocalToIndex); + return this.initPromise; + function addLocalToIndex() { + that.add(that.local); + } + }, + initialize: function initialize(force) { + return !this.initPromise || force ? this._initialize() : this.initPromise; + }, + add: function add(data) { + this.index.add(data); + return this; + }, + get: function get(ids) { + ids = _.isArray(ids) ? ids : [].slice.call(arguments); + return this.index.get(ids); + }, + search: function search(query, sync, async) { + var that = this, local; + local = this.sorter(this.index.search(query)); + sync(this.remote ? local.slice() : local); + if (this.remote && local.length < this.sufficient) { + this.remote.get(query, processRemote); + } else if (this.remote) { + this.remote.cancelLastRequest(); + } + return this; + function processRemote(remote) { + var nonDuplicates = []; + _.each(remote, function(r) { + !_.some(local, function(l) { + return that.identify(r) === that.identify(l); + }) && nonDuplicates.push(r); + }); + async && async(nonDuplicates); + } + }, + all: function all() { + return this.index.all(); + }, + clear: function clear() { + this.index.reset(); + return this; + }, + clearPrefetchCache: function clearPrefetchCache() { + this.prefetch && this.prefetch.clear(); + return this; + }, + clearRemoteCache: function clearRemoteCache() { + Transport.resetCache(); + return this; + }, + ttAdapter: function ttAdapter() { + return this.__ttAdapter(); + } + }); + return Bloodhound; + }(); + return Bloodhound; +}); + +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define("typeahead.js", [ "jquery" ], function(a0) { + return factory(a0); + }); + } else if (typeof exports === "object") { + module.exports = factory(require("jquery")); + } else { + factory(jQuery); + } +})(this, function($) { + var _ = function() { + "use strict"; + return { + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isJQuery: function(obj) { + return obj instanceof $; + }, + toStr: function toStr(s) { + return _.isUndefined(s) || s === null ? "" : s + ""; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); + } + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + identity: function(x) { + return x; + }, + clone: function(obj) { + return $.extend(true, {}, obj); + }, + getIdGenerator: function() { + var counter = 0; + return function() { + return counter++; + }; + }, + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + stringify: function(val) { + return _.isString(val) ? val : JSON.stringify(val); + }, + noop: function() {} + }; + }(); + var WWW = function() { + "use strict"; + var defaultClassNames = { + wrapper: "twitter-typeahead", + input: "tt-input", + hint: "tt-hint", + menu: "tt-menu", + dataset: "tt-dataset", + suggestion: "tt-suggestion", + selectable: "tt-selectable", + empty: "tt-empty", + open: "tt-open", + cursor: "tt-cursor", + highlight: "tt-highlight" + }; + return build; + function build(o) { + var www, classes; + classes = _.mixin({}, defaultClassNames, o); + www = { + css: buildCss(), + classes: classes, + html: buildHtml(classes), + selectors: buildSelectors(classes) + }; + return { + css: www.css, + html: www.html, + classes: www.classes, + selectors: www.selectors, + mixin: function(o) { + _.mixin(o, www); + } + }; + } + function buildHtml(c) { + return { + wrapper: '', + menu: '
    ' + }; + } + function buildSelectors(classes) { + var selectors = {}; + _.each(classes, function(v, k) { + selectors[k] = "." + v; + }); + return selectors; + } + function buildCss() { + var css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none", + opacity: "1" + }, + input: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + inputWithNoHint: { + position: "relative", + verticalAlign: "top" + }, + menu: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + }, + ltr: { + left: "0", + right: "auto" + }, + rtl: { + left: "auto", + right: " 0" + } + }; + if (_.isMsie()) { + _.mixin(css.input, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + return css; + } + }(); + var EventBus = function() { + "use strict"; + var namespace, deprecationMap; + namespace = "typeahead:"; + deprecationMap = { + render: "rendered", + cursorchange: "cursorchanged", + select: "selected", + autocomplete: "autocompleted" + }; + function EventBus(o) { + if (!o || !o.el) { + $.error("EventBus initialized without el"); + } + this.$el = $(o.el); + } + _.mixin(EventBus.prototype, { + _trigger: function(type, args) { + var $e; + $e = $.Event(namespace + type); + (args = args || []).unshift($e); + this.$el.trigger.apply(this.$el, args); + return $e; + }, + before: function(type) { + var args, $e; + args = [].slice.call(arguments, 1); + $e = this._trigger("before" + type, args); + return $e.isDefaultPrevented(); + }, + trigger: function(type) { + var deprecatedType; + this._trigger(type, [].slice.call(arguments, 1)); + if (deprecatedType = deprecationMap[type]) { + this._trigger(deprecatedType, [].slice.call(arguments, 1)); + } + } + }); + return EventBus; + }(); + var EventEmitter = function() { + "use strict"; + var splitter = /\s+/, nextTick = getNextTick(); + return { + onSync: onSync, + onAsync: onAsync, + off: off, + trigger: trigger + }; + function on(method, types, cb, context) { + var type; + if (!cb) { + return this; + } + types = types.split(splitter); + cb = context ? bindContext(cb, context) : cb; + this._callbacks = this._callbacks || {}; + while (type = types.shift()) { + this._callbacks[type] = this._callbacks[type] || { + sync: [], + async: [] + }; + this._callbacks[type][method].push(cb); + } + return this; + } + function onAsync(types, cb, context) { + return on.call(this, "async", types, cb, context); + } + function onSync(types, cb, context) { + return on.call(this, "sync", types, cb, context); + } + function off(types) { + var type; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + while (type = types.shift()) { + delete this._callbacks[type]; + } + return this; + } + function trigger(types) { + var type, callbacks, args, syncFlush, asyncFlush; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + args = [].slice.call(arguments, 1); + while ((type = types.shift()) && (callbacks = this._callbacks[type])) { + syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); + asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); + syncFlush() && nextTick(asyncFlush); + } + return this; + } + function getFlush(callbacks, context, args) { + return flush; + function flush() { + var cancelled; + for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { + cancelled = callbacks[i].apply(context, args) === false; + } + return !cancelled; + } + } + function getNextTick() { + var nextTickFn; + if (window.setImmediate) { + nextTickFn = function nextTickSetImmediate(fn) { + setImmediate(function() { + fn(); + }); + }; + } else { + nextTickFn = function nextTickSetTimeout(fn) { + setTimeout(function() { + fn(); + }, 0); + }; + } + return nextTickFn; + } + function bindContext(fn, context) { + return fn.bind ? fn.bind(context) : function() { + fn.apply(context, [].slice.call(arguments, 0)); + }; + } + }(); + var highlight = function(doc) { + "use strict"; + var defaults = { + node: null, + pattern: null, + tagName: "strong", + className: null, + wordsOnly: false, + caseSensitive: false + }; + return function hightlight(o) { + var regex; + o = _.mixin({}, defaults, o); + if (!o.node || !o.pattern) { + return; + } + o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; + regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); + traverse(o.node, hightlightTextNode); + function hightlightTextNode(textNode) { + var match, patternNode, wrapperNode; + if (match = regex.exec(textNode.data)) { + wrapperNode = doc.createElement(o.tagName); + o.className && (wrapperNode.className = o.className); + patternNode = textNode.splitText(match.index); + patternNode.splitText(match[0].length); + wrapperNode.appendChild(patternNode.cloneNode(true)); + textNode.parentNode.replaceChild(wrapperNode, patternNode); + } + return !!match; + } + function traverse(el, hightlightTextNode) { + var childNode, TEXT_NODE_TYPE = 3; + for (var i = 0; i < el.childNodes.length; i++) { + childNode = el.childNodes[i]; + if (childNode.nodeType === TEXT_NODE_TYPE) { + i += hightlightTextNode(childNode) ? 1 : 0; + } else { + traverse(childNode, hightlightTextNode); + } + } + } + }; + function getRegex(patterns, caseSensitive, wordsOnly) { + var escapedPatterns = [], regexStr; + for (var i = 0, len = patterns.length; i < len; i++) { + escapedPatterns.push(_.escapeRegExChars(patterns[i])); + } + regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; + return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); + } + }(window.document); + var Input = function() { + "use strict"; + var specialKeyCodeMap; + specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + function Input(o, www) { + o = o || {}; + if (!o.input) { + $.error("input is missing"); + } + www.mixin(this); + this.$hint = $(o.hint); + this.$input = $(o.input); + this.query = this.$input.val(); + this.queryWhenFocused = this.hasFocus() ? this.query : null; + this.$overflowHelper = buildOverflowHelper(this.$input); + this._checkLanguageDirection(); + if (this.$hint.length === 0) { + this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; + } + } + Input.normalizeQuery = function(str) { + return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + }; + _.mixin(Input.prototype, EventEmitter, { + _onBlur: function onBlur() { + this.resetInputValue(); + this.trigger("blurred"); + }, + _onFocus: function onFocus() { + this.queryWhenFocused = this.query; + this.trigger("focused"); + }, + _onKeydown: function onKeydown($e) { + var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; + this._managePreventDefault(keyName, $e); + if (keyName && this._shouldTrigger(keyName, $e)) { + this.trigger(keyName + "Keyed", $e); + } + }, + _onInput: function onInput() { + this._setQuery(this.getInputValue()); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); + }, + _managePreventDefault: function managePreventDefault(keyName, $e) { + var preventDefault; + switch (keyName) { + case "up": + case "down": + preventDefault = !withModifier($e); + break; + + default: + preventDefault = false; + } + preventDefault && $e.preventDefault(); + }, + _shouldTrigger: function shouldTrigger(keyName, $e) { + var trigger; + switch (keyName) { + case "tab": + trigger = !withModifier($e); + break; + + default: + trigger = true; + } + return trigger; + }, + _checkLanguageDirection: function checkLanguageDirection() { + var dir = (this.$input.css("direction") || "ltr").toLowerCase(); + if (this.dir !== dir) { + this.dir = dir; + this.$hint.attr("dir", dir); + this.trigger("langDirChanged", dir); + } + }, + _setQuery: function setQuery(val, silent) { + var areEquivalent, hasDifferentWhitespace; + areEquivalent = areQueriesEquivalent(val, this.query); + hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; + this.query = val; + if (!silent && !areEquivalent) { + this.trigger("queryChanged", this.query); + } else if (!silent && hasDifferentWhitespace) { + this.trigger("whitespaceChanged", this.query); + } + }, + bind: function() { + var that = this, onBlur, onFocus, onKeydown, onInput; + onBlur = _.bind(this._onBlur, this); + onFocus = _.bind(this._onFocus, this); + onKeydown = _.bind(this._onKeydown, this); + onInput = _.bind(this._onInput, this); + this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); + if (!_.isMsie() || _.isMsie() > 9) { + this.$input.on("input.tt", onInput); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + _.defer(_.bind(that._onInput, that, $e)); + }); + } + return this; + }, + focus: function focus() { + this.$input.focus(); + }, + blur: function blur() { + this.$input.blur(); + }, + getLangDir: function getLangDir() { + return this.dir; + }, + getQuery: function getQuery() { + return this.query || ""; + }, + setQuery: function setQuery(val, silent) { + this.setInputValue(val); + this._setQuery(val, silent); + }, + hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { + return this.query !== this.queryWhenFocused; + }, + getInputValue: function getInputValue() { + return this.$input.val(); + }, + setInputValue: function setInputValue(value) { + this.$input.val(value); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); + }, + resetInputValue: function resetInputValue() { + this.setInputValue(this.query); + }, + getHint: function getHint() { + return this.$hint.val(); + }, + setHint: function setHint(value) { + this.$hint.val(value); + }, + clearHint: function clearHint() { + this.setHint(""); + }, + clearHintIfInvalid: function clearHintIfInvalid() { + var val, hint, valIsPrefixOfHint, isValid; + val = this.getInputValue(); + hint = this.getHint(); + valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; + isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); + !isValid && this.clearHint(); + }, + hasFocus: function hasFocus() { + return this.$input.is(":focus"); + }, + hasOverflow: function hasOverflow() { + var constraint = this.$input.width() - 2; + this.$overflowHelper.text(this.getInputValue()); + return this.$overflowHelper.width() >= constraint; + }, + isCursorAtEnd: function() { + var valueLength, selectionStart, range; + valueLength = this.$input.val().length; + selectionStart = this.$input[0].selectionStart; + if (_.isNumber(selectionStart)) { + return selectionStart === valueLength; + } else if (document.selection) { + range = document.selection.createRange(); + range.moveStart("character", -valueLength); + return valueLength === range.text.length; + } + return true; + }, + destroy: function destroy() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$overflowHelper.remove(); + this.$hint = this.$input = this.$overflowHelper = $("
    "); + } + }); + return Input; + function buildOverflowHelper($input) { + return $('').css({ + position: "absolute", + visibility: "hidden", + whiteSpace: "pre", + fontFamily: $input.css("font-family"), + fontSize: $input.css("font-size"), + fontStyle: $input.css("font-style"), + fontVariant: $input.css("font-variant"), + fontWeight: $input.css("font-weight"), + wordSpacing: $input.css("word-spacing"), + letterSpacing: $input.css("letter-spacing"), + textIndent: $input.css("text-indent"), + textRendering: $input.css("text-rendering"), + textTransform: $input.css("text-transform") + }).insertAfter($input); + } + function areQueriesEquivalent(a, b) { + return Input.normalizeQuery(a) === Input.normalizeQuery(b); + } + function withModifier($e) { + return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; + } + }(); + var Dataset = function() { + "use strict"; + var keys, nameGenerator; + keys = { + val: "tt-selectable-display", + obj: "tt-selectable-object" + }; + nameGenerator = _.getIdGenerator(); + function Dataset(o, www) { + o = o || {}; + o.templates = o.templates || {}; + o.templates.notFound = o.templates.notFound || o.templates.empty; + if (!o.source) { + $.error("missing source"); + } + if (!o.node) { + $.error("missing node"); + } + if (o.name && !isValidName(o.name)) { + $.error("invalid dataset name: " + o.name); + } + www.mixin(this); + this.highlight = !!o.highlight; + this.name = o.name || nameGenerator(); + this.limit = o.limit || 5; + this.displayFn = getDisplayFn(o.display || o.displayKey); + this.templates = getTemplates(o.templates, this.displayFn); + this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; + this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; + this._resetLastSuggestion(); + this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); + } + Dataset.extractData = function extractData(el) { + var $el = $(el); + if ($el.data(keys.obj)) { + return { + val: $el.data(keys.val) || "", + obj: $el.data(keys.obj) || null + }; + } + return null; + }; + _.mixin(Dataset.prototype, EventEmitter, { + _overwrite: function overwrite(query, suggestions) { + suggestions = suggestions || []; + if (suggestions.length) { + this._renderSuggestions(query, suggestions); + } else if (this.async && this.templates.pending) { + this._renderPending(query); + } else if (!this.async && this.templates.notFound) { + this._renderNotFound(query); + } else { + this._empty(); + } + this.trigger("rendered", this.name, suggestions, false); + }, + _append: function append(query, suggestions) { + suggestions = suggestions || []; + if (suggestions.length && this.$lastSuggestion.length) { + this._appendSuggestions(query, suggestions); + } else if (suggestions.length) { + this._renderSuggestions(query, suggestions); + } else if (!this.$lastSuggestion.length && this.templates.notFound) { + this._renderNotFound(query); + } + this.trigger("rendered", this.name, suggestions, true); + }, + _renderSuggestions: function renderSuggestions(query, suggestions) { + var $fragment; + $fragment = this._getSuggestionsFragment(query, suggestions); + this.$lastSuggestion = $fragment.children().last(); + this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); + }, + _appendSuggestions: function appendSuggestions(query, suggestions) { + var $fragment, $lastSuggestion; + $fragment = this._getSuggestionsFragment(query, suggestions); + $lastSuggestion = $fragment.children().last(); + this.$lastSuggestion.after($fragment); + this.$lastSuggestion = $lastSuggestion; + }, + _renderPending: function renderPending(query) { + var template = this.templates.pending; + this._resetLastSuggestion(); + template && this.$el.html(template({ + query: query, + dataset: this.name + })); + }, + _renderNotFound: function renderNotFound(query) { + var template = this.templates.notFound; + this._resetLastSuggestion(); + template && this.$el.html(template({ + query: query, + dataset: this.name + })); + }, + _empty: function empty() { + this.$el.empty(); + this._resetLastSuggestion(); + }, + _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { + var that = this, fragment; + fragment = document.createDocumentFragment(); + _.each(suggestions, function getSuggestionNode(suggestion) { + var $el, context; + context = that._injectQuery(query, suggestion); + $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); + fragment.appendChild($el[0]); + }); + this.highlight && highlight({ + className: this.classes.highlight, + node: fragment, + pattern: query + }); + return $(fragment); + }, + _getFooter: function getFooter(query, suggestions) { + return this.templates.footer ? this.templates.footer({ + query: query, + suggestions: suggestions, + dataset: this.name + }) : null; + }, + _getHeader: function getHeader(query, suggestions) { + return this.templates.header ? this.templates.header({ + query: query, + suggestions: suggestions, + dataset: this.name + }) : null; + }, + _resetLastSuggestion: function resetLastSuggestion() { + this.$lastSuggestion = $(); + }, + _injectQuery: function injectQuery(query, obj) { + return _.isObject(obj) ? _.mixin({ + _query: query + }, obj) : obj; + }, + update: function update(query) { + var that = this, canceled = false, syncCalled = false, rendered = 0; + this.cancel(); + this.cancel = function cancel() { + canceled = true; + that.cancel = $.noop; + that.async && that.trigger("asyncCanceled", query); + }; + this.source(query, sync, async); + !syncCalled && sync([]); + function sync(suggestions) { + if (syncCalled) { + return; + } + syncCalled = true; + suggestions = (suggestions || []).slice(0, that.limit); + rendered = suggestions.length; + that._overwrite(query, suggestions); + if (rendered < that.limit && that.async) { + that.trigger("asyncRequested", query); + } + } + function async(suggestions) { + suggestions = suggestions || []; + if (!canceled && rendered < that.limit) { + that.cancel = $.noop; + rendered += suggestions.length; + that._append(query, suggestions.slice(0, that.limit - rendered)); + that.async && that.trigger("asyncReceived", query); + } + } + }, + cancel: $.noop, + clear: function clear() { + this._empty(); + this.cancel(); + this.trigger("cleared"); + }, + isEmpty: function isEmpty() { + return this.$el.is(":empty"); + }, + destroy: function destroy() { + this.$el = $("
    "); + } + }); + return Dataset; + function getDisplayFn(display) { + display = display || _.stringify; + return _.isFunction(display) ? display : displayFn; + function displayFn(obj) { + return obj[display]; + } + } + function getTemplates(templates, displayFn) { + return { + notFound: templates.notFound && _.templatify(templates.notFound), + pending: templates.pending && _.templatify(templates.pending), + header: templates.header && _.templatify(templates.header), + footer: templates.footer && _.templatify(templates.footer), + suggestion: templates.suggestion || suggestionTemplate + }; + function suggestionTemplate(context) { + return $("
    ").text(displayFn(context)); + } + } + function isValidName(str) { + return /^[_a-zA-Z0-9-]+$/.test(str); + } + }(); + var Menu = function() { + "use strict"; + function Menu(o, www) { + var that = this; + o = o || {}; + if (!o.node) { + $.error("node is required"); + } + www.mixin(this); + this.$node = $(o.node); + this.query = null; + this.datasets = _.map(o.datasets, initializeDataset); + function initializeDataset(oDataset) { + var node = that.$node.find(oDataset.node).first(); + oDataset.node = node.length ? node : $("
    ").appendTo(that.$node); + return new Dataset(oDataset, www); + } + } + _.mixin(Menu.prototype, EventEmitter, { + _onSelectableClick: function onSelectableClick($e) { + this.trigger("selectableClicked", $($e.currentTarget)); + }, + _onRendered: function onRendered(type, dataset, suggestions, async) { + this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); + this.trigger("datasetRendered", dataset, suggestions, async); + }, + _onCleared: function onCleared() { + this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); + this.trigger("datasetCleared"); + }, + _propagate: function propagate() { + this.trigger.apply(this, arguments); + }, + _allDatasetsEmpty: function allDatasetsEmpty() { + return _.every(this.datasets, isDatasetEmpty); + function isDatasetEmpty(dataset) { + return dataset.isEmpty(); + } + }, + _getSelectables: function getSelectables() { + return this.$node.find(this.selectors.selectable); + }, + _removeCursor: function _removeCursor() { + var $selectable = this.getActiveSelectable(); + $selectable && $selectable.removeClass(this.classes.cursor); + }, + _ensureVisible: function ensureVisible($el) { + var elTop, elBottom, nodeScrollTop, nodeHeight; + elTop = $el.position().top; + elBottom = elTop + $el.outerHeight(true); + nodeScrollTop = this.$node.scrollTop(); + nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); + if (elTop < 0) { + this.$node.scrollTop(nodeScrollTop + elTop); + } else if (nodeHeight < elBottom) { + this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); + } + }, + bind: function() { + var that = this, onSelectableClick; + onSelectableClick = _.bind(this._onSelectableClick, this); + this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); + _.each(this.datasets, function(dataset) { + dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); + }); + return this; + }, + isOpen: function isOpen() { + return this.$node.hasClass(this.classes.open); + }, + open: function open() { + this.$node.addClass(this.classes.open); + }, + close: function close() { + this.$node.removeClass(this.classes.open); + this._removeCursor(); + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$node.attr("dir", dir); + }, + selectableRelativeToCursor: function selectableRelativeToCursor(delta) { + var $selectables, $oldCursor, oldIndex, newIndex; + $oldCursor = this.getActiveSelectable(); + $selectables = this._getSelectables(); + oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; + newIndex = oldIndex + delta; + newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; + newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; + return newIndex === -1 ? null : $selectables.eq(newIndex); + }, + setCursor: function setCursor($selectable) { + this._removeCursor(); + if ($selectable = $selectable && $selectable.first()) { + $selectable.addClass(this.classes.cursor); + this._ensureVisible($selectable); + } + }, + getSelectableData: function getSelectableData($el) { + return $el && $el.length ? Dataset.extractData($el) : null; + }, + getActiveSelectable: function getActiveSelectable() { + var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); + return $selectable.length ? $selectable : null; + }, + getTopSelectable: function getTopSelectable() { + var $selectable = this._getSelectables().first(); + return $selectable.length ? $selectable : null; + }, + update: function update(query) { + var isValidUpdate = query !== this.query; + if (isValidUpdate) { + this.query = query; + _.each(this.datasets, updateDataset); + } + return isValidUpdate; + function updateDataset(dataset) { + dataset.update(query); + } + }, + empty: function empty() { + _.each(this.datasets, clearDataset); + this.query = null; + this.$node.addClass(this.classes.empty); + function clearDataset(dataset) { + dataset.clear(); + } + }, + destroy: function destroy() { + this.$node.off(".tt"); + this.$node = $("
    "); + _.each(this.datasets, destroyDataset); + function destroyDataset(dataset) { + dataset.destroy(); + } + } + }); + return Menu; + }(); + var DefaultMenu = function() { + "use strict"; + var s = Menu.prototype; + function DefaultMenu() { + Menu.apply(this, [].slice.call(arguments, 0)); + } + _.mixin(DefaultMenu.prototype, Menu.prototype, { + open: function open() { + !this._allDatasetsEmpty() && this._show(); + return s.open.apply(this, [].slice.call(arguments, 0)); + }, + close: function close() { + this._hide(); + return s.close.apply(this, [].slice.call(arguments, 0)); + }, + _onRendered: function onRendered() { + if (this._allDatasetsEmpty()) { + this._hide(); + } else { + this.isOpen() && this._show(); + } + return s._onRendered.apply(this, [].slice.call(arguments, 0)); + }, + _onCleared: function onCleared() { + if (this._allDatasetsEmpty()) { + this._hide(); + } else { + this.isOpen() && this._show(); + } + return s._onCleared.apply(this, [].slice.call(arguments, 0)); + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); + return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); + }, + _hide: function hide() { + this.$node.hide(); + }, + _show: function show() { + this.$node.css("display", "block"); + } + }); + return DefaultMenu; + }(); + var Typeahead = function() { + "use strict"; + function Typeahead(o, www) { + var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; + o = o || {}; + if (!o.input) { + $.error("missing input"); + } + if (!o.menu) { + $.error("missing menu"); + } + if (!o.eventBus) { + $.error("missing event bus"); + } + www.mixin(this); + this.eventBus = o.eventBus; + this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; + this.input = o.input; + this.menu = o.menu; + this.enabled = true; + this.active = false; + this.input.hasFocus() && this.activate(); + this.dir = this.input.getLangDir(); + this._hacks(); + this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); + onFocused = c(this, "activate", "open", "_onFocused"); + onBlurred = c(this, "deactivate", "_onBlurred"); + onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); + onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); + onEscKeyed = c(this, "isActive", "_onEscKeyed"); + onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); + onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); + onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); + onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); + onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); + onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); + this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); + } + _.mixin(Typeahead.prototype, { + _hacks: function hacks() { + var $input, $menu; + $input = this.input.$input || $("
    "); + $menu = this.menu.$node || $("
    "); + $input.on("blur.tt", function($e) { + var active, isActive, hasActive; + active = document.activeElement; + isActive = $menu.is(active); + hasActive = $menu.has(active).length > 0; + if (_.isMsie() && (isActive || hasActive)) { + $e.preventDefault(); + $e.stopImmediatePropagation(); + _.defer(function() { + $input.focus(); + }); + } + }); + $menu.on("mousedown.tt", function($e) { + $e.preventDefault(); + }); + }, + _onSelectableClicked: function onSelectableClicked(type, $el) { + this.select($el); + }, + _onDatasetCleared: function onDatasetCleared() { + this._updateHint(); + }, + _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) { + this._updateHint(); + this.eventBus.trigger("render", suggestions, async, dataset); + }, + _onAsyncRequested: function onAsyncRequested(type, dataset, query) { + this.eventBus.trigger("asyncrequest", query, dataset); + }, + _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { + this.eventBus.trigger("asynccancel", query, dataset); + }, + _onAsyncReceived: function onAsyncReceived(type, dataset, query) { + this.eventBus.trigger("asyncreceive", query, dataset); + }, + _onFocused: function onFocused() { + this._minLengthMet() && this.menu.update(this.input.getQuery()); + }, + _onBlurred: function onBlurred() { + if (this.input.hasQueryChangedSinceLastFocus()) { + this.eventBus.trigger("change", this.input.getQuery()); + } + }, + _onEnterKeyed: function onEnterKeyed(type, $e) { + var $selectable; + if ($selectable = this.menu.getActiveSelectable()) { + this.select($selectable) && $e.preventDefault(); + } + }, + _onTabKeyed: function onTabKeyed(type, $e) { + var $selectable; + if ($selectable = this.menu.getActiveSelectable()) { + this.select($selectable) && $e.preventDefault(); + } else if ($selectable = this.menu.getTopSelectable()) { + this.autocomplete($selectable) && $e.preventDefault(); + } + }, + _onEscKeyed: function onEscKeyed() { + this.close(); + }, + _onUpKeyed: function onUpKeyed() { + this.moveCursor(-1); + }, + _onDownKeyed: function onDownKeyed() { + this.moveCursor(+1); + }, + _onLeftKeyed: function onLeftKeyed() { + if (this.dir === "rtl" && this.input.isCursorAtEnd()) { + this.autocomplete(this.menu.getTopSelectable()); + } + }, + _onRightKeyed: function onRightKeyed() { + if (this.dir === "ltr" && this.input.isCursorAtEnd()) { + this.autocomplete(this.menu.getTopSelectable()); + } + }, + _onQueryChanged: function onQueryChanged(e, query) { + this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); + }, + _onWhitespaceChanged: function onWhitespaceChanged() { + this._updateHint(); + }, + _onLangDirChanged: function onLangDirChanged(e, dir) { + if (this.dir !== dir) { + this.dir = dir; + this.menu.setLanguageDirection(dir); + } + }, + _openIfActive: function openIfActive() { + this.isActive() && this.open(); + }, + _minLengthMet: function minLengthMet(query) { + query = _.isString(query) ? query : this.input.getQuery() || ""; + return query.length >= this.minLength; + }, + _updateHint: function updateHint() { + var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; + $selectable = this.menu.getTopSelectable(); + data = this.menu.getSelectableData($selectable); + val = this.input.getInputValue(); + if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { + query = Input.normalizeQuery(val); + escapedQuery = _.escapeRegExChars(query); + frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); + match = frontMatchRegEx.exec(data.val); + match && this.input.setHint(val + match[1]); + } else { + this.input.clearHint(); + } + }, + isEnabled: function isEnabled() { + return this.enabled; + }, + enable: function enable() { + this.enabled = true; + }, + disable: function disable() { + this.enabled = false; + }, + isActive: function isActive() { + return this.active; + }, + activate: function activate() { + if (this.isActive()) { + return true; + } else if (!this.isEnabled() || this.eventBus.before("active")) { + return false; + } else { + this.active = true; + this.eventBus.trigger("active"); + return true; + } + }, + deactivate: function deactivate() { + if (!this.isActive()) { + return true; + } else if (this.eventBus.before("idle")) { + return false; + } else { + this.active = false; + this.close(); + this.eventBus.trigger("idle"); + return true; + } + }, + isOpen: function isOpen() { + return this.menu.isOpen(); + }, + open: function open() { + if (!this.isOpen() && !this.eventBus.before("open")) { + this.menu.open(); + this._updateHint(); + this.eventBus.trigger("open"); + } + return this.isOpen(); + }, + close: function close() { + if (this.isOpen() && !this.eventBus.before("close")) { + this.menu.close(); + this.input.clearHint(); + this.input.resetInputValue(); + this.eventBus.trigger("close"); + } + return !this.isOpen(); + }, + setVal: function setVal(val) { + this.input.setQuery(_.toStr(val)); + }, + getVal: function getVal() { + return this.input.getQuery(); + }, + select: function select($selectable) { + var data = this.menu.getSelectableData($selectable); + if (data && !this.eventBus.before("select", data.obj)) { + this.input.setQuery(data.val, true); + this.eventBus.trigger("select", data.obj); + this.close(); + return true; + } + return false; + }, + autocomplete: function autocomplete($selectable) { + var query, data, isValid; + query = this.input.getQuery(); + data = this.menu.getSelectableData($selectable); + isValid = data && query !== data.val; + if (isValid && !this.eventBus.before("autocomplete", data.obj)) { + this.input.setQuery(data.val); + this.eventBus.trigger("autocomplete", data.obj); + return true; + } + return false; + }, + moveCursor: function moveCursor(delta) { + var query, $candidate, data, payload, cancelMove; + query = this.input.getQuery(); + $candidate = this.menu.selectableRelativeToCursor(delta); + data = this.menu.getSelectableData($candidate); + payload = data ? data.obj : null; + cancelMove = this._minLengthMet() && this.menu.update(query); + if (!cancelMove && !this.eventBus.before("cursorchange", payload)) { + this.menu.setCursor($candidate); + if (data) { + this.input.setInputValue(data.val); + } else { + this.input.resetInputValue(); + this._updateHint(); + } + this.eventBus.trigger("cursorchange", payload); + return true; + } + return false; + }, + destroy: function destroy() { + this.input.destroy(); + this.menu.destroy(); + } + }); + return Typeahead; + function c(ctx) { + var methods = [].slice.call(arguments, 1); + return function() { + var args = [].slice.call(arguments); + _.each(methods, function(method) { + return ctx[method].apply(ctx, args); + }); + }; + } + }(); + (function() { + "use strict"; + var old, keys, methods; + old = $.fn.typeahead; + keys = { + www: "tt-www", + attrs: "tt-attrs", + typeahead: "tt-typeahead" + }; + methods = { + initialize: function initialize(o, datasets) { + var www; + datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); + o = o || {}; + www = WWW(o.classNames); + return this.each(attach); + function attach() { + var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor; + _.each(datasets, function(d) { + d.highlight = !!o.highlight; + }); + $input = $(this); + $wrapper = $(www.html.wrapper); + $hint = $elOrNull(o.hint); + $menu = $elOrNull(o.menu); + defaultHint = o.hint !== false && !$hint; + defaultMenu = o.menu !== false && !$menu; + defaultHint && ($hint = buildHintFromInput($input, www)); + defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); + $hint && $hint.val(""); + $input = prepInput($input, www); + if (defaultHint || defaultMenu) { + $wrapper.css(www.css.wrapper); + $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); + $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); + } + MenuConstructor = defaultMenu ? DefaultMenu : Menu; + eventBus = new EventBus({ + el: $input + }); + input = new Input({ + hint: $hint, + input: $input + }, www); + menu = new MenuConstructor({ + node: $menu, + datasets: datasets + }, www); + typeahead = new Typeahead({ + input: input, + menu: menu, + eventBus: eventBus, + minLength: o.minLength + }, www); + $input.data(keys.www, www); + $input.data(keys.typeahead, typeahead); + } + }, + isEnabled: function isEnabled() { + var enabled; + ttEach(this.first(), function(t) { + enabled = t.isEnabled(); + }); + return enabled; + }, + enable: function enable() { + ttEach(this, function(t) { + t.enable(); + }); + return this; + }, + disable: function disable() { + ttEach(this, function(t) { + t.disable(); + }); + return this; + }, + isActive: function isActive() { + var active; + ttEach(this.first(), function(t) { + active = t.isActive(); + }); + return active; + }, + activate: function activate() { + ttEach(this, function(t) { + t.activate(); + }); + return this; + }, + deactivate: function deactivate() { + ttEach(this, function(t) { + t.deactivate(); + }); + return this; + }, + isOpen: function isOpen() { + var open; + ttEach(this.first(), function(t) { + open = t.isOpen(); + }); + return open; + }, + open: function open() { + ttEach(this, function(t) { + t.open(); + }); + return this; + }, + close: function close() { + ttEach(this, function(t) { + t.close(); + }); + return this; + }, + select: function select(el) { + var success = false, $el = $(el); + ttEach(this.first(), function(t) { + success = t.select($el); + }); + return success; + }, + autocomplete: function autocomplete(el) { + var success = false, $el = $(el); + ttEach(this.first(), function(t) { + success = t.autocomplete($el); + }); + return success; + }, + moveCursor: function moveCursoe(delta) { + var success = false; + ttEach(this.first(), function(t) { + success = t.moveCursor(delta); + }); + return success; + }, + val: function val(newVal) { + var query; + if (!arguments.length) { + ttEach(this.first(), function(t) { + query = t.getVal(); + }); + return query; + } else { + ttEach(this, function(t) { + t.setVal(newVal); + }); + return this; + } + }, + destroy: function destroy() { + ttEach(this, function(typeahead, $input) { + revert($input); + typeahead.destroy(); + }); + return this; + } + }; + $.fn.typeahead = function(method) { + if (methods[method]) { + return methods[method].apply(this, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + $.fn.typeahead.noConflict = function noConflict() { + $.fn.typeahead = old; + return this; + }; + function ttEach($els, fn) { + $els.each(function() { + var $input = $(this), typeahead; + (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); + }); + } + function buildHintFromInput($input, www) { + return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({ + autocomplete: "off", + spellcheck: "false", + tabindex: -1 + }); + } + function prepInput($input, www) { + $input.data(keys.attrs, { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass(www.classes.input).attr({ + autocomplete: "off", + spellcheck: false + }); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input; + } + function getBackgroundStyles($el) { + return { + backgroundAttachment: $el.css("background-attachment"), + backgroundClip: $el.css("background-clip"), + backgroundColor: $el.css("background-color"), + backgroundImage: $el.css("background-image"), + backgroundOrigin: $el.css("background-origin"), + backgroundPosition: $el.css("background-position"), + backgroundRepeat: $el.css("background-repeat"), + backgroundSize: $el.css("background-size") + }; + } + function revert($input) { + var www, $wrapper; + www = $input.data(keys.www); + $wrapper = $input.parent().filter(www.selectors.wrapper); + _.each($input.data(keys.attrs), function(val, key) { + _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); + if ($wrapper.length) { + $input.detach().insertAfter($wrapper); + $wrapper.remove(); + } + } + function $elOrNull(obj) { + var isValid, $el; + isValid = _.isJQuery(obj) || _.isElement(obj); + $el = isValid ? $(obj).first() : []; + return $el.length ? $el : null; + } + })(); +}); \ No newline at end of file diff --git a/app/assets/javascripts/lib/typeahead.js b/app/assets/javascripts/lib/typeahead.js deleted file mode 100644 index 3a413d68..00000000 --- a/app/assets/javascripts/lib/typeahead.js +++ /dev/null @@ -1,1165 +0,0 @@ -/*! - * typeahead.js 0.9.3 - * https://github.com/twitter/typeahead - * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT - */ - -(function($) { - var VERSION = "0.9.3"; - var utils = { - isMsie: function() { - var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); - return match ? parseInt(match[2], 10) : false; - }, - isBlankString: function(str) { - return !str || /^\s*$/.test(str); - }, - escapeRegExChars: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - isString: function(obj) { - return typeof obj === "string"; - }, - isNumber: function(obj) { - return typeof obj === "number"; - }, - isArray: $.isArray, - isFunction: $.isFunction, - isObject: $.isPlainObject, - isUndefined: function(obj) { - return typeof obj === "undefined"; - }, - bind: $.proxy, - bindAll: function(obj) { - var val; - for (var key in obj) { - $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); - } - }, - indexOf: function(haystack, needle) { - for (var i = 0; i < haystack.length; i++) { - if (haystack[i] === needle) { - return i; - } - } - return -1; - }, - each: $.each, - map: $.map, - filter: $.grep, - every: function(obj, test) { - var result = true; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (!(result = test.call(null, val, key, obj))) { - return false; - } - }); - return !!result; - }, - some: function(obj, test) { - var result = false; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (result = test.call(null, val, key, obj)) { - return false; - } - }); - return !!result; - }, - mixin: $.extend, - getUniqueId: function() { - var counter = 0; - return function() { - return counter++; - }; - }(), - defer: function(fn) { - setTimeout(fn, 0); - }, - debounce: function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments, later, callNow; - later = function() { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - } - }; - callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - } - return result; - }; - }, - throttle: function(func, wait) { - var context, args, timeout, result, previous, later; - previous = 0; - later = function() { - previous = new Date(); - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = new Date(), remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - tokenizeQuery: function(str) { - return $.trim(str).toLowerCase().split(/[\s]+/); - }, - tokenizeText: function(str) { - return $.trim(str).toLowerCase().split(/[\s\-_]+/); - }, - getProtocol: function() { - return location.protocol; - }, - noop: function() {} - }; - var EventTarget = function() { - var eventSplitter = /\s+/; - return { - on: function(events, callback) { - var event; - if (!callback) { - return this; - } - this._callbacks = this._callbacks || {}; - events = events.split(eventSplitter); - while (event = events.shift()) { - this._callbacks[event] = this._callbacks[event] || []; - this._callbacks[event].push(callback); - } - return this; - }, - trigger: function(events, data) { - var event, callbacks; - if (!this._callbacks) { - return this; - } - events = events.split(eventSplitter); - while (event = events.shift()) { - if (callbacks = this._callbacks[event]) { - for (var i = 0; i < callbacks.length; i += 1) { - callbacks[i].call(this, { - type: event, - data: data - }); - } - } - } - return this; - } - }; - }(); - var EventBus = function() { - var namespace = "typeahead:"; - function EventBus(o) { - if (!o || !o.el) { - $.error("EventBus initialized without el"); - } - this.$el = $(o.el); - } - utils.mixin(EventBus.prototype, { - trigger: function(type) { - var args = [].slice.call(arguments, 1); - this.$el.trigger(namespace + type, args); - } - }); - return EventBus; - }(); - var PersistentStorage = function() { - var ls, methods; - try { - ls = window.localStorage; - ls.setItem("~~~", "!"); - ls.removeItem("~~~"); - } catch (err) { - ls = null; - } - function PersistentStorage(namespace) { - this.prefix = [ "__", namespace, "__" ].join(""); - this.ttlKey = "__ttl__"; - this.keyMatcher = new RegExp("^" + this.prefix); - } - if (ls && window.JSON) { - methods = { - _prefix: function(key) { - return this.prefix + key; - }, - _ttlKey: function(key) { - return this._prefix(key) + this.ttlKey; - }, - get: function(key) { - if (this.isExpired(key)) { - this.remove(key); - } - return decode(ls.getItem(this._prefix(key))); - }, - set: function(key, val, ttl) { - if (utils.isNumber(ttl)) { - ls.setItem(this._ttlKey(key), encode(now() + ttl)); - } else { - ls.removeItem(this._ttlKey(key)); - } - return ls.setItem(this._prefix(key), encode(val)); - }, - remove: function(key) { - ls.removeItem(this._ttlKey(key)); - ls.removeItem(this._prefix(key)); - return this; - }, - clear: function() { - var i, key, keys = [], len = ls.length; - for (i = 0; i < len; i++) { - if ((key = ls.key(i)).match(this.keyMatcher)) { - keys.push(key.replace(this.keyMatcher, "")); - } - } - for (i = keys.length; i--; ) { - this.remove(keys[i]); - } - return this; - }, - isExpired: function(key) { - var ttl = decode(ls.getItem(this._ttlKey(key))); - return utils.isNumber(ttl) && now() > ttl ? true : false; - } - }; - } else { - methods = { - get: utils.noop, - set: utils.noop, - remove: utils.noop, - clear: utils.noop, - isExpired: utils.noop - }; - } - utils.mixin(PersistentStorage.prototype, methods); - return PersistentStorage; - function now() { - return new Date().getTime(); - } - function encode(val) { - return JSON.stringify(utils.isUndefined(val) ? null : val); - } - function decode(val) { - return JSON.parse(val); - } - }(); - var RequestCache = function() { - function RequestCache(o) { - utils.bindAll(this); - o = o || {}; - this.sizeLimit = o.sizeLimit || 10; - this.cache = {}; - this.cachedKeysByAge = []; - } - utils.mixin(RequestCache.prototype, { - get: function(url) { - return this.cache[url]; - }, - set: function(url, resp) { - var requestToEvict; - if (this.cachedKeysByAge.length === this.sizeLimit) { - requestToEvict = this.cachedKeysByAge.shift(); - delete this.cache[requestToEvict]; - } - this.cache[url] = resp; - this.cachedKeysByAge.push(url); - } - }); - return RequestCache; - }(); - var Transport = function() { - var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; - function Transport(o) { - utils.bindAll(this); - o = utils.isString(o) ? { - url: o - } : o; - requestCache = requestCache || new RequestCache(); - maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; - this.url = o.url; - this.wildcard = o.wildcard || "%QUERY"; - this.filter = o.filter; - this.replace = o.replace; - this.ajaxSettings = { - type: "get", - cache: o.cache, - timeout: o.timeout, - dataType: o.dataType || "json", - beforeSend: o.beforeSend - }; - this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); - } - utils.mixin(Transport.prototype, { - _get: function(url, cb) { - var that = this; - if (belowPendingRequestsThreshold()) { - this._sendRequest(url).done(done); - } else { - this.onDeckRequestArgs = [].slice.call(arguments, 0); - } - function done(resp) { - var data = that.filter ? that.filter(resp) : resp; - cb && cb(data); - requestCache.set(url, resp); - } - }, - _sendRequest: function(url) { - var that = this, jqXhr = pendingRequests[url]; - if (!jqXhr) { - incrementPendingRequests(); - jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); - } - return jqXhr; - function always() { - decrementPendingRequests(); - pendingRequests[url] = null; - if (that.onDeckRequestArgs) { - that._get.apply(that, that.onDeckRequestArgs); - that.onDeckRequestArgs = null; - } - } - }, - get: function(query, cb) { - var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; - cb = cb || utils.noop; - url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); - if (resp = requestCache.get(url)) { - utils.defer(function() { - cb(that.filter ? that.filter(resp) : resp); - }); - } else { - this._get(url, cb); - } - return !!resp; - } - }); - return Transport; - function incrementPendingRequests() { - pendingRequestsCount++; - } - function decrementPendingRequests() { - pendingRequestsCount--; - } - function belowPendingRequestsThreshold() { - return pendingRequestsCount < maxPendingRequests; - } - }(); - var Dataset = function() { - var keys = { - thumbprint: "thumbprint", - protocol: "protocol", - itemHash: "itemHash", - adjacencyList: "adjacencyList" - }; - function Dataset(o) { - utils.bindAll(this); - if (utils.isString(o.template) && !o.engine) { - $.error("no template engine specified"); - } - if (!o.local && !o.prefetch && !o.remote) { - $.error("one of local, prefetch, or remote is required"); - } - this.name = o.name || utils.getUniqueId(); - this.limit = o.limit || 5; - this.minLength = o.minLength || 1; - this.header = o.header; - this.footer = o.footer; - this.valueKey = o.valueKey || "value"; - this.template = compileTemplate(o.template, o.engine, this.valueKey); - this.local = o.local; - this.prefetch = o.prefetch; - this.remote = o.remote; - this.itemHash = {}; - this.adjacencyList = {}; - this.storage = o.name ? new PersistentStorage(o.name) : null; - } - utils.mixin(Dataset.prototype, { - _processLocalData: function(data) { - this._mergeProcessedData(this._processData(data)); - }, - _loadPrefetchData: function(o) { - var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; - if (this.storage) { - storedThumbprint = this.storage.get(keys.thumbprint); - storedProtocol = this.storage.get(keys.protocol); - storedItemHash = this.storage.get(keys.itemHash); - storedAdjacencyList = this.storage.get(keys.adjacencyList); - } - isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); - o = utils.isString(o) ? { - url: o - } : o; - o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; - if (storedItemHash && storedAdjacencyList && !isExpired) { - this._mergeProcessedData({ - itemHash: storedItemHash, - adjacencyList: storedAdjacencyList - }); - deferred = $.Deferred().resolve(); - } else { - deferred = $.getJSON(o.url).done(processPrefetchData); - } - return deferred; - function processPrefetchData(data) { - var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; - if (that.storage) { - that.storage.set(keys.itemHash, itemHash, o.ttl); - that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); - that.storage.set(keys.thumbprint, thumbprint, o.ttl); - that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); - } - that._mergeProcessedData(processedData); - } - }, - _transformDatum: function(datum) { - var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { - value: value, - tokens: tokens - }; - if (utils.isString(datum)) { - item.datum = {}; - item.datum[this.valueKey] = datum; - } else { - item.datum = datum; - } - item.tokens = utils.filter(item.tokens, function(token) { - return !utils.isBlankString(token); - }); - item.tokens = utils.map(item.tokens, function(token) { - return token.toLowerCase(); - }); - return item; - }, - _processData: function(data) { - var that = this, itemHash = {}, adjacencyList = {}; - utils.each(data, function(i, datum) { - var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); - itemHash[id] = item; - utils.each(item.tokens, function(i, token) { - var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); - !~utils.indexOf(adjacency, id) && adjacency.push(id); - }); - }); - return { - itemHash: itemHash, - adjacencyList: adjacencyList - }; - }, - _mergeProcessedData: function(processedData) { - var that = this; - utils.mixin(this.itemHash, processedData.itemHash); - utils.each(processedData.adjacencyList, function(character, adjacency) { - var masterAdjacency = that.adjacencyList[character]; - that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; - }); - }, - _getLocalSuggestions: function(terms) { - var that = this, firstChars = [], lists = [], shortestList, suggestions = []; - utils.each(terms, function(i, term) { - var firstChar = term.charAt(0); - !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); - }); - utils.each(firstChars, function(i, firstChar) { - var list = that.adjacencyList[firstChar]; - if (!list) { - return false; - } - lists.push(list); - if (!shortestList || list.length < shortestList.length) { - shortestList = list; - } - }); - if (lists.length < firstChars.length) { - return []; - } - utils.each(shortestList, function(i, id) { - var item = that.itemHash[id], isCandidate, isMatch; - isCandidate = utils.every(lists, function(list) { - return ~utils.indexOf(list, id); - }); - isMatch = isCandidate && utils.every(terms, function(term) { - return utils.some(item.tokens, function(token) { - return token.indexOf(term) === 0; - }); - }); - isMatch && suggestions.push(item); - }); - return suggestions; - }, - initialize: function() { - var deferred; - this.local && this._processLocalData(this.local); - this.transport = this.remote ? new Transport(this.remote) : null; - deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); - this.local = this.prefetch = this.remote = null; - this.initialize = function() { - return deferred; - }; - return deferred; - }, - getSuggestions: function(query, cb) { - var that = this, terms, suggestions, cacheHit = false; - if (query.length < this.minLength) { - return; - } - terms = utils.tokenizeQuery(query); - suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); - if (suggestions.length < this.limit && this.transport) { - cacheHit = this.transport.get(query, processRemoteData); - } - !cacheHit && cb && cb(suggestions); - function processRemoteData(data) { - suggestions = suggestions.slice(0); - utils.each(data, function(i, datum) { - var item = that._transformDatum(datum), isDuplicate; - isDuplicate = utils.some(suggestions, function(suggestion) { - //return item.value === suggestion.value; - return false; - }); - !isDuplicate && suggestions.push(item); - return suggestions.length < that.limit; - }); - cb && cb(suggestions); - } - } - }); - return Dataset; - function compileTemplate(template, engine, valueKey) { - var renderFn, compiledTemplate; - if (utils.isFunction(template)) { - renderFn = template; - } else if (utils.isString(template)) { - compiledTemplate = engine.compile(template); - renderFn = utils.bind(compiledTemplate.render, compiledTemplate); - } else { - renderFn = function(context) { - return "

    " + context[valueKey] + "

    "; - }; - } - return renderFn; - } - }(); - var InputView = function() { - function InputView(o) { - var that = this; - utils.bindAll(this); - this.specialKeyCodeMap = { - // START METAMAPS CODE - //9: "tab", - // END METAMAPS CODE - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - this.$hint = $(o.hint); - this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); - if (!utils.isMsie()) { - this.$input.on("input.tt", this._compareQueryToInputValue); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - utils.defer(that._compareQueryToInputValue); - }); - } - this.query = this.$input.val(); - this.$overflowHelper = buildOverflowHelper(this.$input); - } - utils.mixin(InputView.prototype, EventTarget, { - _handleFocus: function() { - this.trigger("focused"); - }, - _handleBlur: function() { - this.trigger("blured"); - }, - _handleSpecialKeyEvent: function($e) { - var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; - keyName && this.trigger(keyName + "Keyed", $e); - }, - _compareQueryToInputValue: function() { - var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; - if (isSameQueryExceptWhitespace) { - this.trigger("whitespaceChanged", { - value: this.query - }); - } else if (!isSameQuery) { - this.trigger("queryChanged", { - value: this.query = inputValue - }); - } - }, - destroy: function() { - this.$hint.off(".tt"); - this.$input.off(".tt"); - this.$hint = this.$input = this.$overflowHelper = null; - }, - focus: function() { - this.$input.focus(); - }, - blur: function() { - this.$input.blur(); - }, - getQuery: function() { - return this.query; - }, - setQuery: function(query) { - this.query = query; - }, - getInputValue: function() { - return this.$input.val(); - }, - setInputValue: function(value, silent) { - this.$input.val(value); - !silent && this._compareQueryToInputValue(); - }, - getHintValue: function() { - return this.$hint.val(); - }, - setHintValue: function(value) { - this.$hint.val(value); - }, - getLanguageDirection: function() { - return (this.$input.css("direction") || "ltr").toLowerCase(); - }, - isOverflow: function() { - this.$overflowHelper.text(this.getInputValue()); - return this.$overflowHelper.width() > this.$input.width(); - }, - isCursorAtEnd: function() { - var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; - if (utils.isNumber(selectionStart)) { - return selectionStart === valueLength; - } else if (document.selection) { - range = document.selection.createRange(); - range.moveStart("character", -valueLength); - return valueLength === range.text.length; - } - return true; - } - }); - return InputView; - function buildOverflowHelper($input) { - return $("").css({ - position: "absolute", - left: "-9999px", - visibility: "hidden", - whiteSpace: "nowrap", - fontFamily: $input.css("font-family"), - fontSize: $input.css("font-size"), - fontStyle: $input.css("font-style"), - fontVariant: $input.css("font-variant"), - fontWeight: $input.css("font-weight"), - wordSpacing: $input.css("word-spacing"), - letterSpacing: $input.css("letter-spacing"), - textIndent: $input.css("text-indent"), - textRendering: $input.css("text-rendering"), - textTransform: $input.css("text-transform") - }).insertAfter($input); - } - function compareQueries(a, b) { - a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - return a === b; - } - }(); - var DropdownView = function() { - var html = { - suggestionsList: '' - }, css = { - suggestionsList: { - display: "block" - }, - suggestion: { - whiteSpace: "nowrap", - cursor: "pointer" - }, - suggestionChild: { - whiteSpace: "normal" - } - }; - function DropdownView(o) { - utils.bindAll(this); - this.isOpen = false; - this.isEmpty = true; - this.isMouseOverDropdown = false; - this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover); - } - utils.mixin(DropdownView.prototype, EventTarget, { - _handleMouseenter: function() { - this.isMouseOverDropdown = true; - }, - _handleMouseleave: function() { - this.isMouseOverDropdown = false; - - // START METAMAPS CODE - this._getSuggestions().removeClass("tt-is-under-cursor"); - this._getSuggestions().removeClass("tt-is-under-mouse-cursor"); - // END METAMAPS CODE - }, - _handleMouseover: function($e) { - var $suggestion = $($e.currentTarget); - this._getSuggestions().removeClass("tt-is-under-cursor"); - // START METAMAPS CODE - this._getSuggestions().removeClass("tt-is-under-mouse-cursor"); - $suggestion.addClass("tt-is-under-mouse-cursor"); - // ORIGINAL CODE $suggestion.addClass("tt-is-under-cursor"); - }, - _handleSelection: function($e) { - var $suggestion = $($e.currentTarget); - this.trigger("suggestionSelected", extractSuggestion($suggestion)); - }, - _show: function() { - this.$menu.css("display", "block"); - }, - _hide: function() { - this.$menu.hide(); - }, - _moveCursor: function(increment) { - var $suggestions, $cur, nextIndex, $underCursor; - if (!this.isVisible()) { - return; - } - $suggestions = this._getSuggestions(); - $cur = $suggestions.filter(".tt-is-under-cursor"); - $cur.removeClass("tt-is-under-cursor"); - nextIndex = $suggestions.index($cur) + increment; - nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; - if (nextIndex === -1) { - this.trigger("cursorRemoved"); - return; - } else if (nextIndex < -1) { - nextIndex = $suggestions.length - 1; - } - $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); - this._ensureVisibility($underCursor); - this.trigger("cursorMoved", extractSuggestion($underCursor)); - }, - _getSuggestions: function() { - return this.$menu.find(".tt-suggestions > .tt-suggestion"); - }, - _ensureVisibility: function($el) { - var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true); - if (elTop < 0) { - this.$menu.scrollTop(menuScrollTop + elTop); - } else if (menuHeight < elBottom) { - this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); - } - }, - destroy: function() { - this.$menu.off(".tt"); - this.$menu = null; - }, - isVisible: function() { - return this.isOpen && !this.isEmpty; - }, - closeUnlessMouseIsOverDropdown: function() { - if (!this.isMouseOverDropdown) { - this.close(); - } - }, - close: function() { - if (this.isOpen) { - this.isOpen = false; - this.isMouseOverDropdown = false; - this._hide(); - this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); - this.trigger("closed"); - } - }, - open: function() { - if (!this.isOpen) { - this.isOpen = true; - !this.isEmpty && this._show(); - this.trigger("opened"); - } - }, - setLanguageDirection: function(dir) { - var ltrCss = { - left: "0", - right: "auto" - }, rtlCss = { - left: "auto", - right: " 0" - }; - dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); - }, - moveCursorUp: function() { - this._moveCursor(-1); - }, - moveCursorDown: function() { - this._moveCursor(+1); - }, - getSuggestionUnderCursor: function() { - var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); - return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; - }, - getFirstSuggestion: function() { - var $suggestion = this._getSuggestions().first(); - return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; - }, - renderSuggestions: function(dataset, suggestions) { - var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '
    %body
    ', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el; - if ($dataset.length === 0) { - $suggestionsList = $(html.suggestionsList).css(css.suggestionsList); - $dataset = $("
    ").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu); - } - if (suggestions.length > 0) { - this.isEmpty = false; - this.isOpen && this._show(); - elBuilder = document.createElement("div"); - fragment = document.createDocumentFragment(); - utils.each(suggestions, function(i, suggestion) { - suggestion.dataset = dataset.name; - compiledHtml = dataset.template(suggestion.datum); - elBuilder.innerHTML = wrapper.replace("%body", compiledHtml); - $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion); - $el.children().each(function() { - $(this).css(css.suggestionChild); - }); - fragment.appendChild($el[0]); - }); - $dataset.show().find(".tt-suggestions").html(fragment); - } else { - this.clearSuggestions(dataset.name); - } - this.trigger("suggestionsRendered"); - }, - clearSuggestions: function(datasetName) { - var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions"); - $datasets.hide(); - $suggestions.empty(); - if (this._getSuggestions().length === 0) { - this.isEmpty = true; - this._hide(); - } - } - }); - return DropdownView; - function extractSuggestion($el) { - return $el.data("suggestion"); - } - }(); - var TypeaheadView = function() { - var html = { - wrapper: '', - hint: '', - dropdown: '' - }, css = { - wrapper: { - position: "relative", - display: "inline-block" - }, - hint: { - position: "absolute", - top: "0", - left: "0", - borderColor: "transparent", - boxShadow: "none" - }, - query: { - position: "relative", - verticalAlign: "top", - backgroundColor: "transparent" - }, - dropdown: { - position: "absolute", - top: "100%", - left: "0", - zIndex: "100", - display: "none" - } - }; - if (utils.isMsie()) { - utils.mixin(css.query, { - backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" - }); - } - if (utils.isMsie() && utils.isMsie() <= 7) { - utils.mixin(css.wrapper, { - display: "inline", - zoom: "1" - }); - utils.mixin(css.query, { - marginTop: "-1px" - }); - } - function TypeaheadView(o) { - var $menu, $input, $hint; - utils.bindAll(this); - this.$node = buildDomStructure(o.input); - this.datasets = o.datasets; - this.dir = null; - this.eventBus = o.eventBus; - $menu = this.$node.find(".tt-dropdown-menu"); - $input = this.$node.find(".tt-query"); - $hint = this.$node.find(".tt-hint"); - this.dropdownView = new DropdownView({ - menu: $menu - }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent); - // START METAMAPS CODE - this.dropdownView.on('suggestionsRendered', this._suggestionsRendered); - // END METAMAPS CODE - - this.inputView = new InputView({ - input: $input, - hint: $hint - }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete); - // START METAMAPS CODE - this.inputView.on('queryChanged', this._queryChanged); - // END METAMAPS CODE - } - utils.mixin(TypeaheadView.prototype, EventTarget, { - _managePreventDefault: function(e) { - var $e = e.data, hint, inputValue, preventDefault = false; - switch (e.type) { - case "tabKeyed": - hint = this.inputView.getHintValue(); - inputValue = this.inputView.getInputValue(); - preventDefault = hint && hint !== inputValue; - break; - - case "upKeyed": - case "downKeyed": - preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey; - break; - } - preventDefault && $e.preventDefault(); - }, - _setLanguageDirection: function() { - var dir = this.inputView.getLanguageDirection(); - if (dir !== this.dir) { - this.dir = dir; - this.$node.css("direction", dir); - this.dropdownView.setLanguageDirection(dir); - } - }, - // START METAMAPS CODE - _suggestionsRendered: function() { - this.eventBus.trigger('suggestionsRendered'); - }, - _queryChanged: function() { - this.eventBus.trigger('queryChanged'); - }, - // END METAMAPS CODE - _updateHint: function() { - var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match; - if (hint && dropdownIsVisible && !inputHasOverflow) { - inputValue = this.inputView.getInputValue(); - query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, ""); - escapedQuery = utils.escapeRegExChars(query); - beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); - match = beginsWithQuery.exec(hint); - this.inputView.setHintValue(inputValue + (match ? match[1] : "")); - } - }, - _clearHint: function() { - this.inputView.setHintValue(""); - }, - _clearSuggestions: function() { - this.dropdownView.clearSuggestions(); - }, - _setInputValueToQuery: function() { - this.inputView.setInputValue(this.inputView.getQuery()); - }, - _setInputValueToSuggestionUnderCursor: function(e) { - var suggestion = e.data; - this.inputView.setInputValue(suggestion.value, true); - }, - _openDropdown: function() { - this.dropdownView.open(); - }, - _closeDropdown: function(e) { - this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"](); - }, - _moveDropdownCursor: function(e) { - var $e = e.data; - if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) { - this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"](); - } - }, - _handleSelection: function(e) { - var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor(); - if (suggestion) { - this.inputView.setInputValue(suggestion.value); - byClick ? this.inputView.focus() : e.data.preventDefault(); - byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close(); - this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset); - } - }, - _getSuggestions: function() { - var that = this, query = this.inputView.getQuery(); - if (utils.isBlankString(query)) { - return; - } - utils.each(this.datasets, function(i, dataset) { - dataset.getSuggestions(query, function(suggestions) { - if (query === that.inputView.getQuery()) { - that.dropdownView.renderSuggestions(dataset, suggestions); - } - }); - }); - }, - _autocomplete: function(e) { - var isCursorAtEnd, ignoreEvent, query, hint, suggestion; - if (e.type === "rightKeyed" || e.type === "leftKeyed") { - isCursorAtEnd = this.inputView.isCursorAtEnd(); - ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed"; - if (!isCursorAtEnd || ignoreEvent) { - return; - } - } - query = this.inputView.getQuery(); - hint = this.inputView.getHintValue(); - if (hint !== "" && query !== hint) { - suggestion = this.dropdownView.getFirstSuggestion(); - this.inputView.setInputValue(suggestion.value); - this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset); - } - }, - _propagateEvent: function(e) { - this.eventBus.trigger(e.type); - }, - destroy: function() { - this.inputView.destroy(); - this.dropdownView.destroy(); - destroyDomStructure(this.$node); - this.$node = null; - }, - setQuery: function(query) { - this.inputView.setQuery(query); - this.inputView.setInputValue(query); - this._clearHint(); - this._clearSuggestions(); - this._getSuggestions(); - } - }); - return TypeaheadView; - function buildDomStructure(input) { - var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint); - $wrapper = $wrapper.css(css.wrapper); - $dropdown = $dropdown.css(css.dropdown); - $hint.css(css.hint).css({ - backgroundAttachment: $input.css("background-attachment"), - backgroundClip: $input.css("background-clip"), - backgroundColor: $input.css("background-color"), - backgroundImage: $input.css("background-image"), - backgroundOrigin: $input.css("background-origin"), - backgroundPosition: $input.css("background-position"), - backgroundRepeat: $input.css("background-repeat"), - backgroundSize: $input.css("background-size") - }); - $input.data("ttAttrs", { - dir: $input.attr("dir"), - autocomplete: $input.attr("autocomplete"), - spellcheck: $input.attr("spellcheck"), - style: $input.attr("style") - }); - $input.addClass("tt-query").attr({ - autocomplete: "off", - spellcheck: false - }).css(css.query); - try { - !$input.attr("dir") && $input.attr("dir", "auto"); - } catch (e) {} - return $input.wrap($wrapper).parent().prepend($hint).append($dropdown); - } - function destroyDomStructure($node) { - var $input = $node.find(".tt-query"); - utils.each($input.data("ttAttrs"), function(key, val) { - utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); - }); - $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node); - $node.remove(); - } - }(); - (function() { - var cache = {}, viewKey = "ttView", methods; - methods = { - initialize: function(datasetDefs) { - var datasets; - datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ]; - if (datasetDefs.length === 0) { - $.error("no datasets provided"); - } - datasets = utils.map(datasetDefs, function(o) { - var dataset = cache[o.name] ? cache[o.name] : new Dataset(o); - if (o.name) { - cache[o.name] = dataset; - } - return dataset; - }); - return this.each(initialize); - function initialize() { - var $input = $(this), deferreds, eventBus = new EventBus({ - el: $input - }); - deferreds = utils.map(datasets, function(dataset) { - return dataset.initialize(); - }); - $input.data(viewKey, new TypeaheadView({ - input: $input, - eventBus: eventBus = new EventBus({ - el: $input - }), - datasets: datasets - })); - $.when.apply($, deferreds).always(function() { - utils.defer(function() { - eventBus.trigger("initialized"); - }); - }); - } - }, - destroy: function() { - return this.each(destroy); - function destroy() { - var $this = $(this), view = $this.data(viewKey); - if (view) { - view.destroy(); - $this.removeData(viewKey); - } - } - }, - setQuery: function(query) { - return this.each(setQuery); - function setQuery() { - var view = $(this).data(viewKey); - view && view.setQuery(query); - } - } - }; - jQuery.fn.typeahead = function(method) { - if (methods[method]) { - return methods[method].apply(this, [].slice.call(arguments, 1)); - } else { - return methods.initialize.apply(this, arguments); - } - }; - })(); -})(window.jQuery); diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index af0518d0..842e32b0 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -672,11 +672,15 @@ Metamaps.Create = { [{ name: 'topic_autocomplete', limit: 8, - template: $('#topicAutocompleteTemplate').html(), - remote: { - url: '/topics/autocomplete_topic?term=%QUERY' + template: Hogan.compile($('#topicAutocompleteTemplate').html()), + source: function(query, syncResults, asyncResults) { + syncResults([]); //we don't got none + var url = '/topics/autocomplete_topic?term=' + query; + $.ajax(url, { + success: function(data) { asyncResults(data); }, + error: function() { asyncResults([]); }, + }); }, - engine: Hogan }] ); @@ -733,23 +737,34 @@ Metamaps.Create = { }, [{ name: 'synapse_autocomplete', - template: "
    {{label}}
    ", - remote: { - url: '/search/synapses?term=%QUERY' + template: Hogan.compile("
    {{label}}
    "), + source: function(query, syncResults, asyncResults) { + syncResults([]); //we don't got none + var url = '/search/synapses?term=' + query; + $.ajax(url, { + success: function(data) { asyncResults(data); }, + error: function() { asyncResults([]); }, + }); }, - engine: Hogan }, { name: 'existing_synapses', limit: 50, - template: $('#synapseAutocompleteTemplate').html(), - remote: { - url: '/search/synapses', - replace: function () { - return self.getSearchQuery(); - } + template: Hogan.compile($('#synapseAutocompleteTemplate').html()), + source: function(query, syncResults, asyncResults) { + syncResults([]); //we don't got none + var self = Metamaps.Create.newSynapse; + + if (Metamaps.Selected.Nodes.length < 2) { + var url = '/search/synapses?topic1id=' + self.topic1id + '&topic2id=' + self.topic2id; + $.ajax(url, { + success: function(data) { asyncResults(data); }, + error: function() { asyncResults([]); }, + }); + } else { + asyncResults([]); + } }, - engine: Hogan, header: "

    Existing synapses

    " }] ); @@ -785,13 +800,6 @@ Metamaps.Create = { Metamaps.Mouse.synapseStartCoordinates = []; Metamaps.Visualize.mGraph.plot(); }, - getSearchQuery: function () { - var self = Metamaps.Create.newSynapse; - - if (Metamaps.Selected.Nodes.length < 2) { - return '/search/synapses?topic1id=' + self.topic1id + '&topic2id=' + self.topic2id; - } else return ''; - } } }; // end Metamaps.Create From a51777b0bad7ae7680b0bb3e383b5766231f3418 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 2 Oct 2015 13:36:51 +0800 Subject: [PATCH 030/122] working version of autocomplete that includes the new typeahead.js syntax. So much more complicated than before... --- app/assets/javascripts/src/Metamaps.js | 92 ++++++++++++++++---------- app/views/layouts/_templates.html.erb | 2 + 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index 842e32b0..5f41472d 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -664,6 +664,15 @@ Metamaps.Create = { Metamaps.Create.newTopic.name = $(this).val(); }); + var topicBloodhound = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/topics/autocomplete_topic?term=%QUERY', + wildcard: '%QUERY', + }, + }); + // initialize the autocomplete results for the metacode spinner $('#topic_name').typeahead( { @@ -672,20 +681,18 @@ Metamaps.Create = { [{ name: 'topic_autocomplete', limit: 8, - template: Hogan.compile($('#topicAutocompleteTemplate').html()), - source: function(query, syncResults, asyncResults) { - syncResults([]); //we don't got none - var url = '/topics/autocomplete_topic?term=' + query; - $.ajax(url, { - success: function(data) { asyncResults(data); }, - error: function() { asyncResults([]); }, - }); + display: function (s) { return s.label; }, + templates: { + suggestion: function(s) { + return Hogan.compile($('#topicAutocompleteTemplate').html()).render(s); + }, }, + source: topicBloodhound, }] ); // tell the autocomplete to submit the form with the topic you clicked on if you pick from the autocomplete - $('#topic_name').bind('typeahead:selected', function (event, datum, dataset) { + $('#topic_name').bind('typeahead:select', function (event, datum, dataset) { Metamaps.Topic.getTopicFromAutocomplete(datum.id); }); @@ -718,7 +725,7 @@ Metamaps.Create = { }, hide: function () { $('#new_topic').fadeOut('fast'); - $("#topic_name").typeahead('setQuery', ''); + $("#topic_name").typeahead('val', ''); Metamaps.Create.newTopic.beingCreated = false; } }, @@ -730,6 +737,31 @@ Metamaps.Create = { Metamaps.Create.newSynapse.description = $(this).val(); }); + var synapseBloodhound = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/synapses?term=%QUERY', + wildcard: '%QUERY', + }, + }); + var existingSynapseBloodhound = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/synapses?topic1id=%TOPIC1&topic2id=%TOPIC2', + prepare: function(query, settings) { + var self = Metamaps.Create.newSynapse; + if (Metamaps.Selected.Nodes.length < 2) { + var url = '/search/synapses?topic1id=' + self.topic1id + '&topic2id=' + self.topic2id; + } + console.log(query); + console.log(settings); + return settings; + }, + }, + }); + // initialize the autocomplete results for synapse creation $('#synapse_desc').typeahead( { @@ -737,39 +769,29 @@ Metamaps.Create = { }, [{ name: 'synapse_autocomplete', - template: Hogan.compile("
    {{label}}
    "), - source: function(query, syncResults, asyncResults) { - syncResults([]); //we don't got none - var url = '/search/synapses?term=' + query; - $.ajax(url, { - success: function(data) { asyncResults(data); }, - error: function() { asyncResults([]); }, - }); + display: function(s) { return s.label; }, + templates: { + suggestion: function(s) { + return Hogan.compile("
    {{label}}
    ").render(s); + }, }, + source: synapseBloodhound, }, { name: 'existing_synapses', limit: 50, - template: Hogan.compile($('#synapseAutocompleteTemplate').html()), - source: function(query, syncResults, asyncResults) { - syncResults([]); //we don't got none - var self = Metamaps.Create.newSynapse; - - if (Metamaps.Selected.Nodes.length < 2) { - var url = '/search/synapses?topic1id=' + self.topic1id + '&topic2id=' + self.topic2id; - $.ajax(url, { - success: function(data) { asyncResults(data); }, - error: function() { asyncResults([]); }, - }); - } else { - asyncResults([]); - } + display: function(s) { return s.label; }, + templates: { + suggestion: function(s) { + return Hogan.compile($('#synapseAutocompleteTemplate').html()).render(s); + }, + header: "

    Existing synapses

    " }, - header: "

    Existing synapses

    " + source: existingSynapseBloodhound, }] ); - $('#synapse_desc').bind('typeahead:selected', function (event, datum, dataset) { + $('#synapse_desc').bind('typeahead:select', function (event, datum, dataset) { if (datum.id) { // if they clicked on an existing synapse get it Metamaps.Synapse.getSynapseFromAutocomplete(datum.id); } @@ -792,7 +814,7 @@ Metamaps.Create = { }, hide: function () { $('#new_synapse').fadeOut('fast'); - $("#synapse_desc").typeahead('setQuery', ''); + $("#synapse_desc").typeahead('val', ''); Metamaps.Create.newSynapse.beingCreated = false; Metamaps.Create.newTopic.addSynapse = false; Metamaps.Create.newSynapse.topic1id = 0; diff --git a/app/views/layouts/_templates.html.erb b/app/views/layouts/_templates.html.erb index 4762a271..4218bfda 100644 --- a/app/views/layouts/_templates.html.erb +++ b/app/views/layouts/_templates.html.erb @@ -196,6 +196,7 @@ From 80b2b1876ae32f6c3e8d30fc2c298717bb5f4370 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 2 Oct 2015 14:39:17 +0800 Subject: [PATCH 031/122] deal with https://github.com/twitter/typeahead.js/issues/1195 --- app/assets/javascripts/src/Metamaps.GlobalUI.js | 4 ++-- app/assets/javascripts/src/Metamaps.js | 4 +--- app/assets/stylesheets/application.css | 16 ++++++++-------- app/assets/stylesheets/clean.css | 6 +++--- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/src/Metamaps.GlobalUI.js b/app/assets/javascripts/src/Metamaps.GlobalUI.js index 46a34b5c..656c62e5 100644 --- a/app/assets/javascripts/src/Metamaps.GlobalUI.js +++ b/app/assets/javascripts/src/Metamaps.GlobalUI.js @@ -616,7 +616,7 @@ Metamaps.GlobalUI.Search = { var self = Metamaps.GlobalUI.Search; function toggleResultSet(set) { - var s = $('.tt-dataset-' + set + ' .tt-suggestions'); + var s = $('.tt-dataset-' + set + ' .tt-dataset'); if (s.css('height') == '0px') { s.css({ 'height': 'auto', @@ -658,4 +658,4 @@ Metamaps.GlobalUI.Search = { showLoader: function () { $('#searchLoading').show(); } -}; \ No newline at end of file +}; diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index 5f41472d..cacdd3c3 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -37,7 +37,7 @@ Metamaps.Settings = { background: '#18202E', text: '#DDD' } - } + }, }; Metamaps.Touch = { @@ -1564,8 +1564,6 @@ Metamaps.SynapseCard = { ////////////////////// END TOPIC AND SYNAPSE CARDS ////////////////////////////////// - - /* * * VISUALIZE diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 392b2d2c..6ab5085d 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1373,10 +1373,10 @@ h3.realtimeBoxTitle { /* topic and synapse autocomplete */ -#new_topic .tt-suggestion.tt-is-under-cursor, -#new_topic .tt-suggestion.tt-is-under-mouse-cursor, -#new_synapse .tt-suggestion.tt-is-under-cursor, -#new_synapse .tt-suggestion.tt-is-under-mouse-cursor { +#new_topic .tt-suggestion:hover, +#new_topic .tt-suggestion.tt-cursor, +#new_synapse .tt-suggestion:hover, +#new_synapse .tt-suggestion.tt-cursor { background: #E0E0E0; } #new_topic .tt-suggestion, @@ -1421,12 +1421,12 @@ h3.realtimeBoxTitle { background-image: url(arrowright_sprite.png); background-position: 0 -32px; } -#new_topic .tt-suggestion.tt-is-under-cursor .expandTopicMetadata, -#new_topic .tt-suggestion.tt-is-under-mouse-cursor .expandTopicMetadata { +#new_topic .tt-suggestion:hover .expandTopicMetadata, +#new_topic .tt-suggestion.tt-cursor .expandTopicMetadata { display: block; } -#new_topic .tt-suggestion.tt-is-under-cursor .topicMetadata, -#new_topic .tt-suggestion.tt-is-under-mouse-cursor .topicMetadata { +#new_topic .tt-suggestion:hover .topicMetadata, +#new_topic .tt-suggestion.tt-cursor .topicMetadata { display: block; } #new_topic .topicMetadata { diff --git a/app/assets/stylesheets/clean.css b/app/assets/stylesheets/clean.css index 4ecd3120..d2cc6c7d 100644 --- a/app/assets/stylesheets/clean.css +++ b/app/assets/stylesheets/clean.css @@ -301,7 +301,7 @@ .sidebarSearch .tt-dropdown-menu .maximizeResults { background-position: -32px 0; } -.sidebarSearch .tt-suggestions { +.sidebarSearch .tt-dataset { overflow: visible; } .sidebarSearch .tt-suggestion { @@ -310,7 +310,7 @@ padding: 8px 0; } .sidebarSearch .tt-is-under-cursor, -.sidebarSearch .tt-is-under-mouse-cursor { +.sidebarSearch .tt-suggestion:hover { background: #E0E0E0; } @@ -1227,4 +1227,4 @@ box-shadow: 0px 1px 1.5px rgba(0,0,0,0.12), 0 1px 1px rgba(0,0,0,0.24); body a#barometer_tab:hover { background-position: 0 -110px; -} \ No newline at end of file +} From 120edfc278c6199b14b7df63017eb189586d0274 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 2 Oct 2015 15:46:48 +0800 Subject: [PATCH 032/122] fix typo + debug statements to make existingSynapse autocomplete work --- app/assets/javascripts/src/Metamaps.js | 8 ++++---- app/controllers/main_controller.rb | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index cacdd3c3..e77b9f74 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -753,11 +753,11 @@ Metamaps.Create = { prepare: function(query, settings) { var self = Metamaps.Create.newSynapse; if (Metamaps.Selected.Nodes.length < 2) { - var url = '/search/synapses?topic1id=' + self.topic1id + '&topic2id=' + self.topic2id; + settings.url = settings.url.replace("%TOPIC1", self.topic1id).replace("%TOPIC2", self.topic2id); + return settings; + } else { + return null; } - console.log(query); - console.log(settings); - return settings; }, }, }); diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 1e8b0c9c..88f035e9 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -219,7 +219,7 @@ class MainController < ApplicationController @synapses = [] end - render json: utocomplete_synapse_array_json(@synapses) + render json: autocomplete_synapse_array_json(@synapses) end end From c80569a44964b41bed11e66f1932da44a4dce072 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 2 Oct 2015 15:53:06 +0800 Subject: [PATCH 033/122] bg color on EXisting synapses heading --- app/assets/stylesheets/application.css | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 6ab5085d..b0a1fa97 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1380,6 +1380,7 @@ h3.realtimeBoxTitle { background: #E0E0E0; } #new_topic .tt-suggestion, +#new_synapse .tt-dataset h3, #new_synapse .tt-suggestion { background: #F5F5F5; position: relative; From 69f4b8c645ae1b06a7f248211fc6bd9fe66c5930 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 2 Oct 2015 16:04:30 +0800 Subject: [PATCH 034/122] migrate to polymorphic mappings - DB MIGRATION --- app/models/map.rb | 4 +- app/models/mapping.rb | 8 +- app/models/synapse.rb | 2 +- app/models/topic.rb | 2 +- .../20151001024122_mapping_polymorphism.rb | 31 ++++++++ db/schema.rb | 75 ++++++++++--------- 6 files changed, 80 insertions(+), 42 deletions(-) create mode 100644 db/migrate/20151001024122_mapping_polymorphism.rb diff --git a/app/models/map.rb b/app/models/map.rb index 6262924e..cccde652 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -4,8 +4,8 @@ class Map < ActiveRecord::Base has_many :topicmappings, -> { Mapping.topicmapping }, class_name: :Mapping, dependent: :destroy has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping, dependent: :destroy - has_many :topics, through: :topicmappings - has_many :synapses, through: :synapsemappings + has_many :topics, through: :topicmappings, source: :mappable, source_type: "Topic" + has_many :synapses, through: :synapsemappings, source: :mappable, source_type: "Synapse" # This method associates the attribute ":image" with a file attachment has_attached_file :screenshot, :styles => { diff --git a/app/models/mapping.rb b/app/models/mapping.rb index a8613840..318aa5cf 100644 --- a/app/models/mapping.rb +++ b/app/models/mapping.rb @@ -1,10 +1,10 @@ class Mapping < ActiveRecord::Base - scope :topicmapping, -> { where(category: :Topic) } - scope :synapsemapping, -> { where(category: :Synapse) } + scope :topicmapping, -> { where(mappable_type: :Topic) } + scope :synapsemapping, -> { where(mappable_type: :Synapse) } + + belongs_to :mappable, polymorphic: true - belongs_to :topic, :class_name => "Topic", :foreign_key => "topic_id" - belongs_to :synapse, :class_name => "Synapse", :foreign_key => "synapse_id" belongs_to :map, :class_name => "Map", :foreign_key => "map_id" belongs_to :user diff --git a/app/models/synapse.rb b/app/models/synapse.rb index 10fba6e9..2711c1c5 100644 --- a/app/models/synapse.rb +++ b/app/models/synapse.rb @@ -5,7 +5,7 @@ class Synapse < ActiveRecord::Base belongs_to :topic1, :class_name => "Topic", :foreign_key => "node1_id" belongs_to :topic2, :class_name => "Topic", :foreign_key => "node2_id" - has_many :mappings, dependent: :destroy + has_many :mappings, as: :mappable, dependent: :destroy has_many :maps, :through => :mappings def user_name diff --git a/app/models/topic.rb b/app/models/topic.rb index d28127fa..f82bc256 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -8,7 +8,7 @@ class Topic < ActiveRecord::Base has_many :topics1, :through => :synapses2, :source => :topic1 has_many :topics2, :through => :synapses1, :source => :topic2 - has_many :mappings, dependent: :destroy + has_many :mappings, as: :mappable, dependent: :destroy has_many :maps, :through => :mappings # This method associates the attribute ":image" with a file attachment diff --git a/db/migrate/20151001024122_mapping_polymorphism.rb b/db/migrate/20151001024122_mapping_polymorphism.rb new file mode 100644 index 00000000..6ba88f7c --- /dev/null +++ b/db/migrate/20151001024122_mapping_polymorphism.rb @@ -0,0 +1,31 @@ +class MappingPolymorphism < ActiveRecord::Migration + def up + add_column :mappings, :mappable_id, :integer + add_column :mappings, :mappable_type, :string + add_index :mappings, [:mappable_id, :mappable_type] + + Mapping.find_each do |mapping| + if mapping.synapse_id.nil? and mapping.topic_id.nil? + puts "Mapping id=#{mapping.id} has no valid id, skipping!" + next + end + if not mapping.synapse_id.nil? and not mapping.topic_id.nil? + puts "Mapping id=#{mapping.id} has both topic and synapse ids, skipping!" + next + end + + unless mapping.synapse_id.nil? + mapping.mappable = Synapse.find(mapping.synapse_id) + else + mapping.mappable = Topic.find(mapping.topic_id) + end + mapping.save + end + end + + def down + remove_index :mappings, [:mappable_id, :mappable_type] + remove_column :mappings, :mappable_id, :integer + remove_column :mappings, :mappable_type, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index fc6b7335..ae2c6f03 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,21 +9,24 @@ # from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # -# It's strongly recommended to check this file into your version control system. +# It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(:version => 20141121204712) do +ActiveRecord::Schema.define(version: 20151001024122) do - create_table "in_metacode_sets", :force => true do |t| + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "in_metacode_sets", force: :cascade do |t| t.integer "metacode_id" t.integer "metacode_set_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "in_metacode_sets", ["metacode_id"], :name => "index_in_metacode_sets_on_metacode_id" - add_index "in_metacode_sets", ["metacode_set_id"], :name => "index_in_metacode_sets_on_metacode_set_id" + add_index "in_metacode_sets", ["metacode_id"], name: "index_in_metacode_sets_on_metacode_id", using: :btree + add_index "in_metacode_sets", ["metacode_set_id"], name: "index_in_metacode_sets_on_metacode_set_id", using: :btree - create_table "mappings", :force => true do |t| + create_table "mappings", force: :cascade do |t| t.text "category" t.integer "xloc" t.integer "yloc" @@ -31,18 +34,22 @@ ActiveRecord::Schema.define(:version => 20141121204712) do t.integer "synapse_id" t.integer "map_id" t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "mappable_id" + t.string "mappable_type" end - create_table "maps", :force => true do |t| + add_index "mappings", ["mappable_id", "mappable_type"], name: "index_mappings_on_mappable_id_and_mappable_type", using: :btree + + create_table "maps", force: :cascade do |t| t.text "name" t.boolean "arranged" t.text "desc" t.text "permission" t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "featured" t.string "screenshot_file_name" t.string "screenshot_content_type" @@ -50,26 +57,26 @@ ActiveRecord::Schema.define(:version => 20141121204712) do t.datetime "screenshot_updated_at" end - create_table "metacode_sets", :force => true do |t| + create_table "metacode_sets", force: :cascade do |t| t.string "name" t.text "desc" t.integer "user_id" t.boolean "mapperContributed" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "metacode_sets", ["user_id"], :name => "index_metacode_sets_on_user_id" + add_index "metacode_sets", ["user_id"], name: "index_metacode_sets_on_user_id", using: :btree - create_table "metacodes", :force => true do |t| + create_table "metacodes", force: :cascade do |t| t.text "name" t.string "icon" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "color" end - create_table "synapses", :force => true do |t| + create_table "synapses", force: :cascade do |t| t.text "desc" t.text "category" t.text "weight" @@ -77,19 +84,19 @@ ActiveRecord::Schema.define(:version => 20141121204712) do t.integer "node1_id" t.integer "node2_id" t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "topics", :force => true do |t| + create_table "topics", force: :cascade do |t| t.text "name" t.text "desc" t.text "link" t.text "permission" t.integer "user_id" t.integer "metacode_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "image_file_name" t.string "image_content_type" t.integer "image_file_size" @@ -100,25 +107,25 @@ ActiveRecord::Schema.define(:version => 20141121204712) do t.datetime "audio_updated_at" end - create_table "users", :force => true do |t| + create_table "users", force: :cascade do |t| t.string "name" t.string "email" t.text "settings" - t.string "code", :limit => 8 - t.string "joinedwithcode", :limit => 8 + t.string "code", limit: 8 + t.string "joinedwithcode", limit: 8 t.string "crypted_password" t.string "password_salt" t.string "persistence_token" t.string "perishable_token" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "encrypted_password", :limit => 128, :default => "" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "encrypted_password", limit: 128, default: "" t.string "remember_token" t.datetime "remember_created_at" t.string "reset_password_token" t.datetime "last_sign_in_at" t.string "last_sign_in_ip" - t.integer "sign_in_count", :default => 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.string "current_sign_in_ip" t.datetime "reset_password_sent_at" @@ -130,6 +137,6 @@ ActiveRecord::Schema.define(:version => 20141121204712) do t.integer "generation" end - add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true + add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree end From 6d1e382d5c38fe17a509b030a3e56d7778a9007c Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 2 Oct 2015 16:28:45 +0800 Subject: [PATCH 035/122] change from category/topic_id/synapse_id to mappable_type/mappable_id --- app/assets/javascripts/src/Metamaps.js | 24 ++++++++++++------------ app/controllers/mappings_controller.rb | 2 +- app/controllers/maps_controller.rb | 14 ++------------ 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index e77b9f74..78b6c868 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -457,11 +457,11 @@ Metamaps.Backbone.init = function () { return Metamaps.Map.get(this.get('map_id')); }, getTopic: function () { - if (this.get('category') === 'Topic') return Metamaps.Topic.get(this.get('topic_id')); + if (this.get('mappable_type') === 'Topic') return Metamaps.Topic.get(this.get('mappable_id')); else return false; }, getSynapse: function () { - if (this.get('category') === 'Synapse') return Metamaps.Synapse.get(this.get('synapse_id')); + if (this.get('mappable_type') === 'Synapse') return Metamaps.Synapse.get(this.get('mappable_id')); else return false; } }); @@ -4109,10 +4109,10 @@ Metamaps.Topic = { Metamaps.Topics.add(topic); var mapping = new Metamaps.Backbone.Mapping({ - category: "Topic", xloc: Metamaps.Create.newTopic.x, yloc: Metamaps.Create.newTopic.y, - topic_id: topic.cid + mappable_id: topic.cid, + mappable_type: "Topic", }); Metamaps.Mappings.add(mapping); @@ -4131,10 +4131,10 @@ Metamaps.Topic = { var topic = self.get(id); var mapping = new Metamaps.Backbone.Mapping({ - category: "Topic", xloc: Metamaps.Create.newTopic.x, yloc: Metamaps.Create.newTopic.y, - topic_id: topic.id + mappable_type: "Topic", + mappable_id: topic.id, }); Metamaps.Mappings.add(mapping); @@ -4149,10 +4149,10 @@ Metamaps.Topic = { var nextCoords = Metamaps.Map.getNextCoord(); var mapping = new Metamaps.Backbone.Mapping({ - category: "Topic", xloc: nextCoords.x, yloc: nextCoords.y, - topic_id: topic.id + mappable_type: "Topic", + mappable_id: topic.id, }); Metamaps.Mappings.add(mapping); @@ -4287,8 +4287,8 @@ Metamaps.Synapse = { Metamaps.Synapses.add(synapse); mapping = new Metamaps.Backbone.Mapping({ - category: "Synapse", - synapse_id: synapse.cid + mappable_type: "Synapse", + mappable_id: synapse.cid, }); Metamaps.Mappings.add(mapping); @@ -4308,8 +4308,8 @@ Metamaps.Synapse = { var synapse = self.get(id); var mapping = new Metamaps.Backbone.Mapping({ - category: "Synapse", - synapse_id: synapse.id + mappable_type: "Synapse", + mappable_id: synapse.id, }); Metamaps.Mappings.add(mapping); diff --git a/app/controllers/mappings_controller.rb b/app/controllers/mappings_controller.rb index 79d8d80a..27567eb4 100644 --- a/app/controllers/mappings_controller.rb +++ b/app/controllers/mappings_controller.rb @@ -52,6 +52,6 @@ class MappingsController < ApplicationController private # Never trust parameters from the scary internet, only allow the white list through. def mapping_params - params.require(:mapping).permit(:id, :category, :xloc, :yloc, :topic_id, :synapse_id, :map_id, :user_id) + params.require(:mapping).permit(:id, :xloc, :yloc, :mappable_id, :mappable_type, :map_id, :user_id) end end diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index 21946f88..7fbddbcc 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -75,11 +75,7 @@ class MapsController < ApplicationController @alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } @allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } @allmappings = @map.mappings.to_a.delete_if {|m| - if m.category == "Synapse" - object = m.synapse - elsif m.category == "Topic" - object = m.topic - end + object = m.mappable !object || (object.permission == "private" && (!authenticated? || (authenticated? && @current.id != object.user_id))) } @@ -103,11 +99,7 @@ class MapsController < ApplicationController @alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } @allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } @allmappings = @map.mappings.to_a.delete_if {|m| - if m.category == "Synapse" - object = m.synapse - elsif m.category == "Topic" - object = m.topic - end + object = m.mappable !object || (object.permission == "private" && (!authenticated? || (authenticated? && @current.id != object.user_id))) } @@ -141,7 +133,6 @@ class MapsController < ApplicationController @all.each do |topic| topic = topic.split('/') @mapping = Mapping.new() - @mapping.category = "Topic" @mapping.user = @user @mapping.map = @map @mapping.topic = Topic.find(topic[0]) @@ -155,7 +146,6 @@ class MapsController < ApplicationController @synAll = @synAll.split(',') @synAll.each do |synapse_id| @mapping = Mapping.new() - @mapping.category = "Synapse" @mapping.user = @user @mapping.map = @map @mapping.synapse = Synapse.find(synapse_id) From 85b3012cd940ec82c01a6dce64f8217472a8991b Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 2 Oct 2015 17:39:53 +0800 Subject: [PATCH 036/122] fix a few more mappable/topic/synapse things in JS --- app/assets/javascripts/src/Metamaps.js | 6 ++++-- app/controllers/main_controller.rb | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index 78b6c868..5f71007d 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -194,7 +194,8 @@ Metamaps.Backbone.init = function () { return Metamaps.Mappings.findWhere({ map_id: Metamaps.Active.Map.id, - topic_id: this.isNew() ? this.cid : this.id + mappable_type: "Topic", + mappable_id: this.isNew() ? this.cid : this.id }); }, createNode: function () { @@ -370,7 +371,8 @@ Metamaps.Backbone.init = function () { return Metamaps.Mappings.findWhere({ map_id: Metamaps.Active.Map.id, - synapse_id: this.isNew() ? this.cid : this.id + mappable_type: "Synapse", + mappable_id: this.isNew() ? this.cid : this.id }); }, createEdge: function () { diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 88f035e9..c5535276 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -187,14 +187,14 @@ class MainController < ApplicationController term = params[:term] topic1id = params[:topic1id] topic2id = params[:topic2id] - + if term && !term.empty? - @synapses = Synapse.select('DISTINCT "desc"').where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"') + @synapses = Synapse.where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"') # remove any duplicate synapse types that just differ by # leading or trailing whitespaces collectedDesc = [] - @synapses.to_a.delete_if {|s| + @synapses.to_a.uniq(&:desc).delete_if {|s| desc = s.desc == nil || s.desc == "" ? "" : s.desc.strip if collectedDesc.index(desc) == nil collectedDesc.push(desc) From ddfb3aa98f3535b764377f751dcdec302720035c Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 21:03:49 +0800 Subject: [PATCH 037/122] fix mapping problem --- app/assets/javascripts/src/Metamaps.js | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index 5f71007d..a05b6fb7 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -157,7 +157,7 @@ Metamaps.Backbone.init = function () { this.on('saved', this.savedEvent); this.on('nowPrivate', function(){ var removeTopicData = { - topicid: this.id + mappableid: this.id }; $(document).trigger(Metamaps.JIT.events.removeTopic, [removeTopicData]); @@ -165,7 +165,7 @@ Metamaps.Backbone.init = function () { this.on('noLongerPrivate', function(){ var newTopicData = { mappingid: this.getMapping().id, - topicid: this.id + mappableid: this.id }; $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]); @@ -321,14 +321,14 @@ Metamaps.Backbone.init = function () { this.on('noLongerPrivate', function(){ var newSynapseData = { mappingid: this.getMapping().id, - synapseid: this.id + mappableid: this.id }; $(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData]); }); this.on('nowPrivate', function(){ $(document).trigger(Metamaps.JIT.events.removeSynapse, [{ - synapseid: this.id + mappableid: this.id }]); }); @@ -2559,7 +2559,7 @@ Metamaps.Realtime = { Metamaps.Mapper.get(data.mapperid, mapperCallback); } $.ajax({ - url: "/topics/" + data.topicid + ".json", + url: "/topics/" + data.mappableid + ".json", success: function (response) { Metamaps.Topics.add(response); topic = Metamaps.Topics.get(response.id); @@ -2606,7 +2606,7 @@ Metamaps.Realtime = { if (!self.status) return; - var topic = Metamaps.Topics.get(data.topicid); + var topic = Metamaps.Topics.get(data.mappableid); if (topic) { var node = topic.get('node'); var mapping = topic.getMapping(); @@ -2657,7 +2657,7 @@ Metamaps.Realtime = { Metamaps.Mapper.get(data.mapperid, mapperCallback); } $.ajax({ - url: "/synapses/" + data.synapseid + ".json", + url: "/synapses/" + data.mappableid + ".json", success: function (response) { Metamaps.Synapses.add(response); synapse = Metamaps.Synapses.get(response.id); @@ -2704,7 +2704,7 @@ Metamaps.Realtime = { if (!self.status) return; - var synapse = Metamaps.Synapses.get(data.synapseid); + var synapse = Metamaps.Synapses.get(data.mappableid); if (synapse) { var edge = synapse.get('edge'); var mapping = synapse.getMapping(); @@ -2814,12 +2814,12 @@ Metamaps.Control = { var permToDelete = Metamaps.Active.Mapper.id === topic.get('user_id') || Metamaps.Active.Mapper.get('admin'); if (permToDelete) { - var topicid = topic.id; + var mappableid = topic.id; var mapping = node.getData('mapping'); topic.destroy(); Metamaps.Mappings.remove(mapping); $(document).trigger(Metamaps.JIT.events.deleteTopic, [{ - topicid: topicid + mappableid: mappableid }]); Metamaps.Control.hideNode(nodeid); } else { @@ -2858,12 +2858,12 @@ Metamaps.Control = { } var topic = node.getData('topic'); - var topicid = topic.id; + var mappableid = topic.id; var mapping = node.getData('mapping'); mapping.destroy(); Metamaps.Topics.remove(topic); $(document).trigger(Metamaps.JIT.events.removeTopic, [{ - topicid: topicid + mappableid: mappableid }]); Metamaps.Control.hideNode(nodeid); }, @@ -2987,7 +2987,7 @@ Metamaps.Control = { Metamaps.Control.hideEdge(edge); } - var synapseid = synapse.id; + var mappableid = synapse.id; synapse.destroy(); // the server will destroy the mapping, we just need to remove it here @@ -2998,7 +2998,7 @@ Metamaps.Control = { delete edge.data.$displayIndex; } $(document).trigger(Metamaps.JIT.events.deleteSynapse, [{ - synapseid: synapseid + mappableid: mappableid }]); } else { Metamaps.GlobalUI.notifyUser('Only synapses you created can be deleted'); @@ -3043,7 +3043,7 @@ Metamaps.Control = { var synapse = edge.getData("synapses")[index]; var mapping = edge.getData("mappings")[index]; - var synapseid = synapse.id; + var mappableid = synapse.id; mapping.destroy(); Metamaps.Synapses.remove(synapse); @@ -3054,7 +3054,7 @@ Metamaps.Control = { delete edge.data.$displayIndex; } $(document).trigger(Metamaps.JIT.events.removeSynapse, [{ - synapseid: synapseid + mappableid: mappableid }]); }, hideSelectedEdges: function () { @@ -4054,14 +4054,14 @@ Metamaps.Topic = { var mappingSuccessCallback = function (mappingModel, response) { var newTopicData = { mappingid: mappingModel.id, - topicid: mappingModel.get('topic_id') + mappableid: mappingModel.get('mappable_id') }; $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]); }; var topicSuccessCallback = function (topicModel, response) { if (Metamaps.Active.Map) { - mapping.save({ topic_id: topicModel.id }, { + mapping.save({ mappable_id: topicModel.id }, { success: mappingSuccessCallback, error: function (model, response) { console.log('error saving mapping to database'); @@ -4225,14 +4225,14 @@ Metamaps.Synapse = { var mappingSuccessCallback = function (mappingModel, response) { var newSynapseData = { mappingid: mappingModel.id, - synapseid: mappingModel.get('synapse_id') + mappableid: mappingModel.get('mappable_id') }; $(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData]); }; var synapseSuccessCallback = function (synapseModel, response) { if (Metamaps.Active.Map) { - mapping.save({ synapse_id: synapseModel.id }, { + mapping.save({ mappable_id: synapseModel.id }, { success: mappingSuccessCallback }); } From 63db41698e313293488d7db6f51a1bcd48ab21d9 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 21:04:16 +0800 Subject: [PATCH 038/122] function naming --- app/assets/javascripts/src/Metamaps.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index a05b6fb7..1e284a6b 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -2541,7 +2541,7 @@ Metamaps.Realtime = { if (!self.status) return; - function test() { + function waitThenRenderTopic() { if (topic && mapping && mapper) { Metamaps.Topic.renderTopic(mapping, topic, false, false); } @@ -2579,7 +2579,7 @@ Metamaps.Realtime = { } }); - test(); + waitThenRenderTopic(); }, // removeTopic sendDeleteTopic: function (data) { @@ -2634,7 +2634,7 @@ Metamaps.Realtime = { if (!self.status) return; - function test() { + function waitThenRenderSynapse() { if (synapse && mapping && mapper) { topic1 = synapse.getTopic1(); node1 = topic1.get('node'); @@ -2676,7 +2676,7 @@ Metamaps.Realtime = { cancel = true; } }); - test(); + waitThenRenderSynapse(); }, // deleteSynapse sendDeleteSynapse: function (data) { From 588b62b942c20944442d68cfd7f5e4ff55a9b847 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 22:07:44 +0800 Subject: [PATCH 039/122] fix problem with mappings and forking maps --- app/controllers/maps_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index 7fbddbcc..add8a0c1 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -135,7 +135,7 @@ class MapsController < ApplicationController @mapping = Mapping.new() @mapping.user = @user @mapping.map = @map - @mapping.topic = Topic.find(topic[0]) + @mapping.mappable = Topic.find(topic[0]) @mapping.xloc = topic[1] @mapping.yloc = topic[2] @mapping.save @@ -148,7 +148,7 @@ class MapsController < ApplicationController @mapping = Mapping.new() @mapping.user = @user @mapping.map = @map - @mapping.synapse = Synapse.find(synapse_id) + @mapping.mappable = Synapse.find(synapse_id) @mapping.save end end From 96871cadab1a0e9b6d5d931fdfe5f65fe737405e Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 22:10:45 +0800 Subject: [PATCH 040/122] brute force prevent nil synapse descriptions --- app/assets/javascripts/src/Metamaps.js | 2 +- app/controllers/synapses_controller.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index 1e284a6b..aea20ab9 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -4282,7 +4282,7 @@ Metamaps.Synapse = { node1 = synapsesToCreate[i]; topic1 = node1.getData('topic'); synapse = new Metamaps.Backbone.Synapse({ - desc: Metamaps.Create.newSynapse.description, + desc: Metamaps.Create.newSynapse.description || "", node1_id: topic1.isNew() ? topic1.cid : topic1.id, node2_id: topic2.isNew() ? topic2.cid : topic2.id, }); diff --git a/app/controllers/synapses_controller.rb b/app/controllers/synapses_controller.rb index 47d3321e..dc36ff28 100644 --- a/app/controllers/synapses_controller.rb +++ b/app/controllers/synapses_controller.rb @@ -22,6 +22,7 @@ class SynapsesController < ApplicationController # POST /synapses.json def create @synapse = Synapse.new(synapse_params) + @synapse.update_attribute :desc, "" if @synapse.desc.nil? respond_to do |format| if @synapse.save @@ -36,6 +37,7 @@ class SynapsesController < ApplicationController # PUT /synapses/1.json def update @synapse = Synapse.find(params[:id]) + @synapse.update_attribute :desc, "" if @synapse.desc.nil? respond_to do |format| if @synapse.update_attributes(synapse_params) From dc66ac10a9a179f35326fd2c2d6e0b4ce363ca3a Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 22:27:33 +0800 Subject: [PATCH 041/122] pry byebug --- Gemfile | 1 + Gemfile.lock | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Gemfile b/Gemfile index 0aa716f8..24fe17fe 100644 --- a/Gemfile +++ b/Gemfile @@ -45,6 +45,7 @@ end group :development, :test do gem 'pry-rails' + gem 'pry-byebug' gem 'better_errors' gem 'quiet_assets' end diff --git a/Gemfile.lock b/Gemfile.lock index 8bd00a57..bff8145f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,6 +52,8 @@ GEM erubis (>= 2.6.6) rack (>= 0.9.0) builder (3.2.2) + byebug (4.0.5) + columnize (= 0.9.0) cancancan (1.12.0) climate_control (0.0.3) activesupport (>= 3.0) @@ -65,6 +67,7 @@ GEM coffee-script-source execjs coffee-script-source (1.9.1.1) + columnize (0.9.0) devise (3.5.2) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -123,6 +126,9 @@ GEM coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) + pry-byebug (3.1.0) + byebug (~> 4.0) + pry (~> 0.10) pry-rails (0.3.4) pry (>= 0.9.10) quiet_assets (1.1.0) @@ -214,6 +220,7 @@ DEPENDENCIES kaminari paperclip pg + pry-byebug pry-rails quiet_assets rails (= 4.2.4) @@ -223,3 +230,6 @@ DEPENDENCIES sass-rails uglifier uservoice-ruby + +BUNDLED WITH + 1.10.6 From 582ef636358a09ff94ae672f91da5717e5e8a55e Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 22:32:09 +0800 Subject: [PATCH 042/122] fix migration for heroku --- db/migrate/20151001024122_mapping_polymorphism.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/migrate/20151001024122_mapping_polymorphism.rb b/db/migrate/20151001024122_mapping_polymorphism.rb index 6ba88f7c..e41233f6 100644 --- a/db/migrate/20151001024122_mapping_polymorphism.rb +++ b/db/migrate/20151001024122_mapping_polymorphism.rb @@ -17,6 +17,7 @@ class MappingPolymorphism < ActiveRecord::Migration unless mapping.synapse_id.nil? mapping.mappable = Synapse.find(mapping.synapse_id) else + next if mapping.topic_id == 0 mapping.mappable = Topic.find(mapping.topic_id) end mapping.save From e078b595033e02edac14ff9ac745e68cd3e7f1a3 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 22:38:42 +0800 Subject: [PATCH 043/122] add missing indexes for speed --- db/migrate/20151023143719_add_missing_indexes.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 db/migrate/20151023143719_add_missing_indexes.rb diff --git a/db/migrate/20151023143719_add_missing_indexes.rb b/db/migrate/20151023143719_add_missing_indexes.rb new file mode 100644 index 00000000..551f2319 --- /dev/null +++ b/db/migrate/20151023143719_add_missing_indexes.rb @@ -0,0 +1,16 @@ +class AddMissingIndexes < ActiveRecord::Migration + def change + add_index :topics, :user_id + add_index :topics, :metacode_id + add_index :synapses, [:node2_id, :node2_id] + add_index :synapses, [:node1_id, :node1_id] + add_index :synapses, :user_id + add_index :synapses, :node1_id + add_index :synapses, :node2_id + add_index :mappings, [:map_id, :topic_id] + add_index :mappings, [:map_id, :synapse_id] + add_index :mappings, :map_id + add_index :mappings, :user_id + add_index :maps, :user_id + end +end From ad47275f72902c28c71ff35788be9a6306aca2d8 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 22:51:16 +0800 Subject: [PATCH 044/122] rename js files to js.erb files --- app/assets/javascripts/src/{JIT.js => JIT.js.erb} | 0 .../src/{Metamaps.Backbone.js => Metamaps.Backbone.js.erb} | 0 .../src/{Metamaps.GlobalUI.js => Metamaps.GlobalUI.js.erb} | 0 .../javascripts/src/{Metamaps.JIT.js => Metamaps.JIT.js.erb} | 0 .../src/{Metamaps.Router.js => Metamaps.Router.js.erb} | 0 .../src/{Metamaps.Views.js => Metamaps.Views.js.erb} | 0 app/assets/javascripts/src/{Metamaps.js => Metamaps.js.erb} | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) rename app/assets/javascripts/src/{JIT.js => JIT.js.erb} (100%) rename app/assets/javascripts/src/{Metamaps.Backbone.js => Metamaps.Backbone.js.erb} (100%) rename app/assets/javascripts/src/{Metamaps.GlobalUI.js => Metamaps.GlobalUI.js.erb} (100%) rename app/assets/javascripts/src/{Metamaps.JIT.js => Metamaps.JIT.js.erb} (100%) rename app/assets/javascripts/src/{Metamaps.Router.js => Metamaps.Router.js.erb} (100%) rename app/assets/javascripts/src/{Metamaps.Views.js => Metamaps.Views.js.erb} (100%) rename app/assets/javascripts/src/{Metamaps.js => Metamaps.js.erb} (97%) diff --git a/app/assets/javascripts/src/JIT.js b/app/assets/javascripts/src/JIT.js.erb similarity index 100% rename from app/assets/javascripts/src/JIT.js rename to app/assets/javascripts/src/JIT.js.erb diff --git a/app/assets/javascripts/src/Metamaps.Backbone.js b/app/assets/javascripts/src/Metamaps.Backbone.js.erb similarity index 100% rename from app/assets/javascripts/src/Metamaps.Backbone.js rename to app/assets/javascripts/src/Metamaps.Backbone.js.erb diff --git a/app/assets/javascripts/src/Metamaps.GlobalUI.js b/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb similarity index 100% rename from app/assets/javascripts/src/Metamaps.GlobalUI.js rename to app/assets/javascripts/src/Metamaps.GlobalUI.js.erb diff --git a/app/assets/javascripts/src/Metamaps.JIT.js b/app/assets/javascripts/src/Metamaps.JIT.js.erb similarity index 100% rename from app/assets/javascripts/src/Metamaps.JIT.js rename to app/assets/javascripts/src/Metamaps.JIT.js.erb diff --git a/app/assets/javascripts/src/Metamaps.Router.js b/app/assets/javascripts/src/Metamaps.Router.js.erb similarity index 100% rename from app/assets/javascripts/src/Metamaps.Router.js rename to app/assets/javascripts/src/Metamaps.Router.js.erb diff --git a/app/assets/javascripts/src/Metamaps.Views.js b/app/assets/javascripts/src/Metamaps.Views.js.erb similarity index 100% rename from app/assets/javascripts/src/Metamaps.Views.js rename to app/assets/javascripts/src/Metamaps.Views.js.erb diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js.erb similarity index 97% rename from app/assets/javascripts/src/Metamaps.js rename to app/assets/javascripts/src/Metamaps.js.erb index aea20ab9..99377757 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js.erb @@ -4282,7 +4282,7 @@ Metamaps.Synapse = { node1 = synapsesToCreate[i]; topic1 = node1.getData('topic'); synapse = new Metamaps.Backbone.Synapse({ - desc: Metamaps.Create.newSynapse.description || "", + desc: Metamaps.Create.newSynapse.description,// || "", node1_id: topic1.isNew() ? topic1.cid : topic1.id, node2_id: topic2.isNew() ? topic2.cid : topic2.id, }); From b070bb9e46f58abd83d996f3f376ffd11697cecf Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 22:56:09 +0800 Subject: [PATCH 045/122] convert javascript to asset_path syntax --- app/assets/javascripts/src/JIT.js.erb | 2 +- app/assets/javascripts/src/Metamaps.GlobalUI.js.erb | 4 ++-- app/assets/javascripts/src/Metamaps.JIT.js.erb | 4 ++-- app/assets/javascripts/src/Metamaps.js.erb | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/src/JIT.js.erb b/app/assets/javascripts/src/JIT.js.erb index 79954271..81cc687d 100644 --- a/app/assets/javascripts/src/JIT.js.erb +++ b/app/assets/javascripts/src/JIT.js.erb @@ -3127,7 +3127,7 @@ var Canvas; ctx = base.getCtx(), scale = base.scaleOffsetX; //var pattern = new Image(); - //pattern.src = "/assets/cubes.png"; + //pattern.src = "<%= asset_path('cubes.png') %>"; //var ptrn = ctx.createPattern(pattern, 'repeat'); //ctx.fillStyle = ptrn; ctx.fillStyle = Metamaps.Settings.colors.background; diff --git a/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb b/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb index 656c62e5..5c575e88 100644 --- a/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb +++ b/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb @@ -495,7 +495,7 @@ Metamaps.GlobalUI.Search = { dataset.push({ value: "No results", label: "No results", - typeImageURL: "/assets/icons/wildcard.png", + typeImageURL: "<%= asset_path('icons/wildcard.png') %>", rtype: "noresult" }); } @@ -549,7 +549,7 @@ Metamaps.GlobalUI.Search = { filter: function (dataset) { if (dataset.length == 0) { dataset.push({ - profile: "/assets/user.png", + profile: "<%= asset_path('user.png') %>", value: "No results", label: "No results", diff --git a/app/assets/javascripts/src/Metamaps.JIT.js.erb b/app/assets/javascripts/src/Metamaps.JIT.js.erb index bef4ad59..a0ddf146 100644 --- a/app/assets/javascripts/src/Metamaps.JIT.js.erb +++ b/app/assets/javascripts/src/Metamaps.JIT.js.erb @@ -29,10 +29,10 @@ Metamaps.JIT = { $(".takeScreenshot").click(Metamaps.Map.exportImage); self.topicDescImage = new Image(); - self.topicDescImage.src = '/assets/topic_description_signifier.png'; + self.topicDescImage.src = '<%= asset_path('topic_description_signifier.png') %>'; self.topicLinkImage = new Image(); - self.topicLinkImage.src = '/assets/topic_link_signifier.png'; + self.topicLinkImage.src =<%= asset_path('topic_link_signifier.png') %>'; }, /** * convert our topic JSON into something JIT can use diff --git a/app/assets/javascripts/src/Metamaps.js.erb b/app/assets/javascripts/src/Metamaps.js.erb index 99377757..9846f4ea 100644 --- a/app/assets/javascripts/src/Metamaps.js.erb +++ b/app/assets/javascripts/src/Metamaps.js.erb @@ -337,7 +337,7 @@ Metamaps.Backbone.init = function () { prepareLiForFilter: function () { var li = ''; li += '
  • ';       - li += '"'; li += ' alt="synapse icon" />';       li += '

    ' + this.get('desc') + '

  • '; return li; @@ -4791,7 +4791,7 @@ Metamaps.Map.InfoBox = { obj["map_creator_tip"] = isCreator ? self.changePermissionText : ""; obj["contributors_class"] = Metamaps.Mappers.length > 1 ? "multiple" : ""; obj["contributors_class"] += Metamaps.Mappers.length === 2 ? " mTwo" : ""; - obj["contributor_image"] = Metamaps.Mappers.length > 0 ? Metamaps.Mappers.models[0].get("image") : "/assets/user.png"; + obj["contributor_image"] = Metamaps.Mappers.length > 0 ? Metamaps.Mappers.models[0].get("image") : "<%= asset_path('user.png') %>"; obj["contributor_list"] = self.createContributorList(); obj["user_name"] = isCreator ? "You" : map.get("user_name"); obj["created_at"] = map.get("created_at_clean"); @@ -4888,7 +4888,7 @@ Metamaps.Map.InfoBox = { if (Metamaps.Mappers.length === 2) contributors_class = "multiple mTwo"; else if (Metamaps.Mappers.length > 2) contributors_class = "multiple"; - var contributors_image = "/assets/user.png"; + var contributors_image = "<%= asset_path('user.png') %>"; if (Metamaps.Mappers.length > 0) { // get the first contributor and use their image contributors_image = Metamaps.Mappers.models[0].get("image"); @@ -5069,7 +5069,7 @@ Metamaps.Account = { var self = Metamaps.Account; $('.userImageDiv canvas').remove(); - $('.userImageDiv img').attr('src', '/assets/user.png').show(); + $('.userImageDiv img').attr('src', '<%= asset_path('user.png') %>').show(); $('.userImageMenu').hide(); var input = $('#user_image'); From 2e51c3c11886b2f86a871ae2c9130f9478b1711c Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 23:01:02 +0800 Subject: [PATCH 046/122] syntax --- app/assets/javascripts/src/Metamaps.JIT.js.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/src/Metamaps.JIT.js.erb b/app/assets/javascripts/src/Metamaps.JIT.js.erb index a0ddf146..38a2de5b 100644 --- a/app/assets/javascripts/src/Metamaps.JIT.js.erb +++ b/app/assets/javascripts/src/Metamaps.JIT.js.erb @@ -32,7 +32,7 @@ Metamaps.JIT = { self.topicDescImage.src = '<%= asset_path('topic_description_signifier.png') %>'; self.topicLinkImage = new Image(); - self.topicLinkImage.src =<%= asset_path('topic_link_signifier.png') %>'; + self.topicLinkImage.src = '<%= asset_path('topic_link_signifier.png') %>'; }, /** * convert our topic JSON into something JIT can use From d539846c6154ed5319ea2e974cba6da5a61cc155 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 23:06:24 +0800 Subject: [PATCH 047/122] asset_path --- app/helpers/maps_helper.rb | 2 +- app/models/map.rb | 2 +- app/models/user.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/helpers/maps_helper.rb b/app/helpers/maps_helper.rb index 0c9b3a08..6b3dd7b0 100644 --- a/app/helpers/maps_helper.rb +++ b/app/helpers/maps_helper.rb @@ -16,7 +16,7 @@ module MapsHelper map['rtype'] = "map" contributorTip = '' - firstContributorImage = '/assets/user.png' + firstContributorImage = asset_path('user.png') if m.contributors.count > 0 firstContributorImage = m.contributors[0].image.url(:thirtytwo) m.contributors.each_with_index do |c, index| diff --git a/app/models/map.rb b/app/models/map.rb index cccde652..6d2a3332 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -12,7 +12,7 @@ class Map < ActiveRecord::Base :thumb => ['188x126#', :png] #:full => ['940x630#', :png] }, - :default_url => "/assets/missing-map.png" + :default_url => asset_path('missing-map.png') # Validate the attached image is image/jpg, image/png, etc validates_attachment_content_type :screenshot, :content_type => /\Aimage\/.*\Z/ diff --git a/app/models/user.rb b/app/models/user.rb index 44bd6b4a..eb69829d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -35,7 +35,7 @@ class User < ActiveRecord::Base :ninetysix => ['96x96#', :png], :onetwentyeight => ['128x128#', :png] }, - :default_url => "/assets/user.png" + :default_url => asset_path('user.png') # Validate the attached image is image/jpg, image/png, etc validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/ From 837abbe9ee4dcc8405dbbf6d4b2fa8886667cb77 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 23:09:14 +0800 Subject: [PATCH 048/122] asset_path doesn't work in models --- app/models/map.rb | 2 +- app/models/user.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/map.rb b/app/models/map.rb index 6d2a3332..eab88814 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -12,7 +12,7 @@ class Map < ActiveRecord::Base :thumb => ['188x126#', :png] #:full => ['940x630#', :png] }, - :default_url => asset_path('missing-map.png') + :default_url => ActionController::Base.helpers.asset_path('missing-map.png') # Validate the attached image is image/jpg, image/png, etc validates_attachment_content_type :screenshot, :content_type => /\Aimage\/.*\Z/ diff --git a/app/models/user.rb b/app/models/user.rb index eb69829d..effa6ec5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -35,7 +35,7 @@ class User < ActiveRecord::Base :ninetysix => ['96x96#', :png], :onetwentyeight => ['128x128#', :png] }, - :default_url => asset_path('user.png') + :default_url => ActionController::Base.helpers.asset_path('user.png') # Validate the attached image is image/jpg, image/png, etc validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/ From 1670235172b8cda52beb4df3573ffe440027450a Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 23:19:25 +0800 Subject: [PATCH 049/122] change some css to erb --- app/assets/stylesheets/{application.css => application.css.erb} | 2 +- app/assets/stylesheets/{clean.css => clean.css.erb} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename app/assets/stylesheets/{application.css => application.css.erb} (95%) rename app/assets/stylesheets/{clean.css => clean.css.erb} (99%) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css.erb similarity index 95% rename from app/assets/stylesheets/application.css rename to app/assets/stylesheets/application.css.erb index b0a1fa97..dfb5aa78 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css.erb @@ -813,7 +813,7 @@ li.accountInvite span { padding: 9px 0 9px 62px; } .accountImage { - background-image: url(user.png); + background-image: url(<%= asset_data_uri 'user.png' %>); background-size: 84px 84px; background-repeat: no-repeat; height:84px; diff --git a/app/assets/stylesheets/clean.css b/app/assets/stylesheets/clean.css.erb similarity index 99% rename from app/assets/stylesheets/clean.css rename to app/assets/stylesheets/clean.css.erb index d2cc6c7d..793e328c 100644 --- a/app/assets/stylesheets/clean.css +++ b/app/assets/stylesheets/clean.css.erb @@ -918,7 +918,7 @@ .takeScreenshot { margin-bottom: 5px; border-radius: 2px; - background-image: url(screenshot_sprite.png); + background-image: url(<%= asset_data_uri 'screenshot_sprite.png' %>); display: none; } .takeScreenshot:hover { From e572389c490f44561bcfd0914f635c7d8b81732d Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 23:22:54 +0800 Subject: [PATCH 050/122] add indexes to schema --- db/schema.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index ae2c6f03..97bf204a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151001024122) do +ActiveRecord::Schema.define(version: 20151023143719) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -40,7 +40,11 @@ ActiveRecord::Schema.define(version: 20151001024122) do t.string "mappable_type" end + add_index "mappings", ["map_id", "synapse_id"], name: "index_mappings_on_map_id_and_synapse_id", using: :btree + add_index "mappings", ["map_id", "topic_id"], name: "index_mappings_on_map_id_and_topic_id", using: :btree + add_index "mappings", ["map_id"], name: "index_mappings_on_map_id", using: :btree add_index "mappings", ["mappable_id", "mappable_type"], name: "index_mappings_on_mappable_id_and_mappable_type", using: :btree + add_index "mappings", ["user_id"], name: "index_mappings_on_user_id", using: :btree create_table "maps", force: :cascade do |t| t.text "name" @@ -57,6 +61,8 @@ ActiveRecord::Schema.define(version: 20151001024122) do t.datetime "screenshot_updated_at" end + add_index "maps", ["user_id"], name: "index_maps_on_user_id", using: :btree + create_table "metacode_sets", force: :cascade do |t| t.string "name" t.text "desc" @@ -88,6 +94,12 @@ ActiveRecord::Schema.define(version: 20151001024122) do t.datetime "updated_at", null: false end + add_index "synapses", ["node1_id", "node1_id"], name: "index_synapses_on_node1_id_and_node1_id", using: :btree + add_index "synapses", ["node1_id"], name: "index_synapses_on_node1_id", using: :btree + add_index "synapses", ["node2_id", "node2_id"], name: "index_synapses_on_node2_id_and_node2_id", using: :btree + add_index "synapses", ["node2_id"], name: "index_synapses_on_node2_id", using: :btree + add_index "synapses", ["user_id"], name: "index_synapses_on_user_id", using: :btree + create_table "topics", force: :cascade do |t| t.text "name" t.text "desc" @@ -107,6 +119,9 @@ ActiveRecord::Schema.define(version: 20151001024122) do t.datetime "audio_updated_at" end + add_index "topics", ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree + add_index "topics", ["user_id"], name: "index_topics_on_user_id", using: :btree + create_table "users", force: :cascade do |t| t.string "name" t.string "email" From 72e67c188af9e0e802c0120c3ccb6192101cb4bf Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 23:23:23 +0800 Subject: [PATCH 051/122] resume ignoring public/assets --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 75aa3c26..2b6ac398 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ realtime/node_modules config/database.yml .env -#public/assets +public/assets # Ignore bundler config .bundle From 46dd54a1d7e898428a9563501f4c2d80a111af7b Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 23:34:18 +0800 Subject: [PATCH 052/122] asset_path in css erb files --- app/assets/stylesheets/application.css.erb | 6 +++--- .../stylesheets/{base.css => base.css.erb} | 4 ++-- app/assets/stylesheets/clean.css.erb | 20 +++++++++---------- app/controllers/synapses_controller.rb | 1 + app/models/synapse.rb | 2 ++ 5 files changed, 18 insertions(+), 15 deletions(-) rename app/assets/stylesheets/{base.css => base.css.erb} (94%) diff --git a/app/assets/stylesheets/application.css.erb b/app/assets/stylesheets/application.css.erb index dfb5aa78..ed4ec2ac 100644 --- a/app/assets/stylesheets/application.css.erb +++ b/app/assets/stylesheets/application.css.erb @@ -2352,15 +2352,15 @@ and it won't be important on password protected instances */ opacity: 0.7; } #chromeIcon { - background: url(/assets/browser_icons.png) no-repeat; + background: url(<%= asset_data_uri 'browser_icons.png' %>) no-repeat; } #fireFoxIcon { - background: url(/assets/browser_icons.png) no-repeat -105px 0; + background: url(<%= asset_data_uri 'browser_icons.png' %>) no-repeat -105px 0; } #safariIcon { - background: url(/assets/browser_icons.png) no-repeat -220px 0; + background: url(<%= asset_data_uri 'browser_icons.png' %>) no-repeat -220px 0; } #chromeIcon:hover{ diff --git a/app/assets/stylesheets/base.css b/app/assets/stylesheets/base.css.erb similarity index 94% rename from app/assets/stylesheets/base.css rename to app/assets/stylesheets/base.css.erb index 2e0073a0..858163ad 100644 --- a/app/assets/stylesheets/base.css +++ b/app/assets/stylesheets/base.css.erb @@ -740,7 +740,7 @@ font-family: 'din-regular', helvetica, sans-serif; } #linkremove { - background-image: url(/assets/remove.png); + background-image: url(<%= asset_data_uri 'remove.png' %>); background-repeat: no-repeat; background-position: center center; width: 24px; @@ -1126,4 +1126,4 @@ font-family: 'din-regular', helvetica, sans-serif; } .mapperMetadata .metadataSynapses { color: #DAB539; -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/clean.css.erb b/app/assets/stylesheets/clean.css.erb index 793e328c..456c6afc 100644 --- a/app/assets/stylesheets/clean.css.erb +++ b/app/assets/stylesheets/clean.css.erb @@ -1,20 +1,20 @@ @font-face { font-family: 'din-medium'; - src: url('/assets/Fonts/din.eot'); - src: url('/assets/Fonts/din.eot?#iefix') format('embedded-opentype'), - url('/assets/Fonts/din.woff') format('woff'), - url('/assets/Fonts/din.ttf') format('truetype'), - url('/assets/Fonts/din.svg#din-medium') format('svg'); + src: url(<%= asset_path 'Fonts/din.eot' %>); + src: url(<%= asset_path 'Fonts/din.eot?#iefix' %>) format('embedded-opentype'), + url(<%= asset_path 'Fonts/din.woff' %>) format('woff'), + url(<%= asset_path 'Fonts/din.ttf' %>) format('truetype'), + url(<%= asset_path 'Fonts/din.svg#din-medium' %>) format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'din-regular'; - src: url('/assets/Fonts/din-reg.eot'); - src: url('/assets/Fonts/din-reg.eot?#iefix') format('embedded-opentype'), - url('/assets/Fonts/din-reg.woff') format('woff'), - url('/assets/Fonts/din-reg.ttf') format('truetype'), - url('/assets/Fonts/din-reg.svg#din-reg') format('svg'); + src: url(<%= asset_path 'Fonts/din-reg.eot' %>); + src: url(<%= asset_path 'Fonts/din-reg.eot?#iefix' %>) format('embedded-opentype'), + url(<%= asset_path 'Fonts/din-reg.woff' %>) format('woff'), + url(<%= asset_path 'Fonts/din-reg.ttf' %>) format('truetype'), + url(<%= asset_path 'Fonts/din-reg.svg#din-reg' %>) format('svg'); font-weight: normal; font-style: normal; } diff --git a/app/controllers/synapses_controller.rb b/app/controllers/synapses_controller.rb index dc36ff28..db30a058 100644 --- a/app/controllers/synapses_controller.rb +++ b/app/controllers/synapses_controller.rb @@ -21,6 +21,7 @@ class SynapsesController < ApplicationController # POST /synapses # POST /synapses.json def create + binding.pry @synapse = Synapse.new(synapse_params) @synapse.update_attribute :desc, "" if @synapse.desc.nil? diff --git a/app/models/synapse.rb b/app/models/synapse.rb index 2711c1c5..2745106f 100644 --- a/app/models/synapse.rb +++ b/app/models/synapse.rb @@ -8,6 +8,8 @@ class Synapse < ActiveRecord::Base has_many :mappings, as: :mappable, dependent: :destroy has_many :maps, :through => :mappings + validates :desc, length: { minimum: 0, allow_nil: false } + def user_name self.user.name end From b00784731bed1ebc8a8578aadd7bca8961113a2c Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Oct 2015 23:42:21 +0800 Subject: [PATCH 053/122] remove binding.pry --- app/controllers/synapses_controller.rb | 1 - app/views/maps/_newtopic.html.erb | 2 +- app/views/metacode_sets/_form.html.erb | 8 ++++---- app/views/metacode_sets/index.html.erb | 2 +- app/views/metacodes/index.html.erb | 4 ++-- app/views/metacodes/new.html.erb | 2 +- app/views/shared/_cheatsheet.html.erb | 2 +- app/views/shared/_metacodeoptions.html.erb | 4 ++-- app/views/shared/_switchmetacodes.html.erb | 4 ++-- app/views/topics/_new.html.erb | 2 +- 10 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/controllers/synapses_controller.rb b/app/controllers/synapses_controller.rb index db30a058..dc36ff28 100644 --- a/app/controllers/synapses_controller.rb +++ b/app/controllers/synapses_controller.rb @@ -21,7 +21,6 @@ class SynapsesController < ApplicationController # POST /synapses # POST /synapses.json def create - binding.pry @synapse = Synapse.new(synapse_params) @synapse.update_attribute :desc, "" if @synapse.desc.nil? diff --git a/app/views/maps/_newtopic.html.erb b/app/views/maps/_newtopic.html.erb index 16b5fecb..e5263d76 100644 --- a/app/views/maps/_newtopic.html.erb +++ b/app/views/maps/_newtopic.html.erb @@ -4,7 +4,7 @@ <% @metacodes = user_metacodes() %> <% set = get_metacodeset() %> <% @metacodes.each do |metacode| %> - <%= metacode.name %> + <%= metacode.name %> <% end %>
    <%= form.text_field :name, :maxlength => 140, :placeholder => "title..." %> diff --git a/app/views/metacode_sets/_form.html.erb b/app/views/metacode_sets/_form.html.erb index e3d1ae40..f7ef60c6 100644 --- a/app/views/metacode_sets/_form.html.erb +++ b/app/views/metacode_sets/_form.html.erb @@ -37,7 +37,7 @@ <% while $i < (Metacode.all.length / 4) do %>
  • class="toggledOff"<% end %> onclick="Metamaps.Admin.liClickHandler.call(this);"> - <%= @m[$i].name %> + <%= @m[$i].name %>

    <%= @m[$i].name.downcase %>

  • @@ -48,7 +48,7 @@ <% while $i < (Metacode.all.length / 4 * 2) do %>
  • class="toggledOff"<% end %> onclick="Metamaps.Admin.liClickHandler.call(this);"> - <%= @m[$i].name %> + <%= @m[$i].name %>

    <%= @m[$i].name.downcase %>

  • @@ -59,7 +59,7 @@ <% while $i < (Metacode.all.length / 4 * 3) do %>
  • class="toggledOff"<% end %> onclick="Metamaps.Admin.liClickHandler.call(this);"> - <%= @m[$i].name %> + <%= @m[$i].name %>

    <%= @m[$i].name.downcase %>

  • @@ -70,7 +70,7 @@ <% while $i < Metacode.all.length do %>
  • class="toggledOff"<% end %> onclick="Metamaps.Admin.liClickHandler.call(this);"> - <%= @m[$i].name %> + <%= @m[$i].name %>

    <%= @m[$i].name.downcase %>

  • diff --git a/app/views/metacode_sets/index.html.erb b/app/views/metacode_sets/index.html.erb index ba83b0a3..8f6aa810 100644 --- a/app/views/metacode_sets/index.html.erb +++ b/app/views/metacode_sets/index.html.erb @@ -23,7 +23,7 @@ <%= metacode_set.desc %> <% metacode_set.metacodes.each_with_index do |metacode, index| %> - + <% if (index+1)%4 == 0 %>
    <% end %> diff --git a/app/views/metacodes/index.html.erb b/app/views/metacodes/index.html.erb index 1ebdb5da..4f3563f1 100644 --- a/app/views/metacodes/index.html.erb +++ b/app/views/metacodes/index.html.erb @@ -14,7 +14,7 @@ <% @metacodes.each do |metacode| %> <%= metacode.name %> - <%= metacode.icon %> + <%= asset_path metacode.icon %> <% if metacode.color %> <%= metacode.color %> @@ -22,7 +22,7 @@ <% else %> <% end %> - + <%= link_to 'Edit', edit_metacode_path(metacode), :data => { :bypass => 'true'} %> <% end %> diff --git a/app/views/metacodes/new.html.erb b/app/views/metacodes/new.html.erb index 8520bb6c..e10f28d1 100644 --- a/app/views/metacodes/new.html.erb +++ b/app/views/metacodes/new.html.erb @@ -2,4 +2,4 @@
    <%= render 'form' %>
    -
    \ No newline at end of file +
    diff --git a/app/views/shared/_cheatsheet.html.erb b/app/views/shared/_cheatsheet.html.erb index 0af70ec1..7332a1c7 100644 --- a/app/views/shared/_cheatsheet.html.erb +++ b/app/views/shared/_cheatsheet.html.erb @@ -61,7 +61,7 @@ Change Topic permission: Click on 'Permission' icon (only for topic creator)
    - Open Topic view: Click on icon within topic card bar + Open Topic view: Click on icon within topic card bar
    Close 'Topic' card: Click on canvas diff --git a/app/views/shared/_metacodeoptions.html.erb b/app/views/shared/_metacodeoptions.html.erb index 25aae79e..a6092c3e 100644 --- a/app/views/shared/_metacodeoptions.html.erb +++ b/app/views/shared/_metacodeoptions.html.erb @@ -12,7 +12,7 @@