├── Rakefile ├── Gemfile ├── lib ├── bootstrap-editable-rails │ ├── version.rb │ └── view_helper.rb └── bootstrap-editable-rails.rb ├── app └── assets │ ├── images │ └── bootstrap-editable │ │ ├── clear.png │ │ └── loading.gif │ ├── stylesheets │ ├── inputs-ext │ │ ├── address │ │ │ └── address.css │ │ ├── wysihtml5 │ │ │ └── bootstrap-wysihtml5-0.0.2 │ │ │ │ ├── wysiwyg-color.css │ │ │ │ └── bootstrap-wysihtml5-0.0.2.css │ │ └── typeaheadjs │ │ │ └── lib │ │ │ └── typeahead.js-bootstrap.css │ └── bootstrap-editable.scss │ └── javascripts │ ├── bootstrap-editable-rails.js.coffee │ ├── inputs-ext │ ├── typeaheadjs │ │ ├── typeaheadjs.js │ │ └── lib │ │ │ └── typeahead.js │ ├── wysihtml5 │ │ ├── wysihtml5.js │ │ └── bootstrap-wysihtml5-0.0.2 │ │ │ ├── bootstrap-wysihtml5-0.0.2.min.js │ │ │ └── bootstrap-wysihtml5-0.0.2.js │ └── address │ │ └── address.js │ └── bootstrap-editable.min.js ├── .gitignore ├── bootstrap-editable-rails.gemspec ├── LICENSE.txt └── README.md /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in bootstrap-editable-rails.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /lib/bootstrap-editable-rails/version.rb: -------------------------------------------------------------------------------- 1 | module Bootstrap 2 | module Editable 3 | module Rails 4 | VERSION = "0.0.9" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/assets/images/bootstrap-editable/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootstrap-ruby/bootstrap-editable-rails/HEAD/app/assets/images/bootstrap-editable/clear.png -------------------------------------------------------------------------------- /app/assets/images/bootstrap-editable/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootstrap-ruby/bootstrap-editable-rails/HEAD/app/assets/images/bootstrap-editable/loading.gif -------------------------------------------------------------------------------- /app/assets/stylesheets/inputs-ext/address/address.css: -------------------------------------------------------------------------------- 1 | .editable-address { 2 | display: block; 3 | margin-bottom: 5px; 4 | } 5 | 6 | .editable-address span { 7 | width: 70px; 8 | display: inline-block; 9 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /lib/bootstrap-editable-rails/view_helper.rb: -------------------------------------------------------------------------------- 1 | module Bootstrap::Editable::Rails 2 | module ViewHelper 3 | # Returns +text+ transformed into HTML suitable for textarea input. 4 | def textarea_format(text) 5 | html_escape(text).gsub(/\r\n|\r|\n/, '
').html_safe 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/bootstrap-editable-rails.rb: -------------------------------------------------------------------------------- 1 | require 'bootstrap-editable-rails/version' 2 | require 'bootstrap-editable-rails/view_helper' 3 | 4 | module Bootstrap 5 | module Editable 6 | module Rails 7 | class Engine < ::Rails::Engine 8 | initializer 'bootstrap-editable-rails' do |app| 9 | ::ActiveSupport.on_load(:action_view) do 10 | ::ActionView::Base.send :include, Bootstrap::Editable::Rails::ViewHelper 11 | end 12 | app.config.assets.precompile << %r(bootstrap-editable/.*\.(?:png|gif)$) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /bootstrap-editable-rails.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'bootstrap-editable-rails/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "bootstrap-editable-rails" 8 | gem.version = Bootstrap::Editable::Rails::VERSION 9 | gem.authors = ["Toru KAWAMURA"] 10 | gem.email = ["tkawa@4bit.net"] 11 | gem.description = %q{In-place editing with Twitter Bootstrap for Rails} 12 | gem.summary = %q{In-place editing with Twitter Bootstrap for Rails} 13 | gem.homepage = "https://github.com/bootstrap-ruby/bootstrap-editable-rails" 14 | 15 | gem.files = `git ls-files`.split($/) 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ["lib"] 19 | 20 | gem.add_dependency "railties", ">= 3.1" 21 | gem.add_dependency "sass-rails" 22 | end 23 | -------------------------------------------------------------------------------- /app/assets/stylesheets/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/wysiwyg-color.css: -------------------------------------------------------------------------------- 1 | .wysiwyg-color-black { 2 | color: black; 3 | } 4 | 5 | .wysiwyg-color-silver { 6 | color: silver; 7 | } 8 | 9 | .wysiwyg-color-gray { 10 | color: gray; 11 | } 12 | 13 | .wysiwyg-color-white { 14 | color: white; 15 | } 16 | 17 | .wysiwyg-color-maroon { 18 | color: maroon; 19 | } 20 | 21 | .wysiwyg-color-red { 22 | color: red; 23 | } 24 | 25 | .wysiwyg-color-purple { 26 | color: purple; 27 | } 28 | 29 | .wysiwyg-color-fuchsia { 30 | color: fuchsia; 31 | } 32 | 33 | .wysiwyg-color-green { 34 | color: green; 35 | } 36 | 37 | .wysiwyg-color-lime { 38 | color: lime; 39 | } 40 | 41 | .wysiwyg-color-olive { 42 | color: olive; 43 | } 44 | 45 | .wysiwyg-color-yellow { 46 | color: yellow; 47 | } 48 | 49 | .wysiwyg-color-navy { 50 | color: navy; 51 | } 52 | 53 | .wysiwyg-color-blue { 54 | color: blue; 55 | } 56 | 57 | .wysiwyg-color-teal { 58 | color: teal; 59 | } 60 | 61 | .wysiwyg-color-aqua { 62 | color: aqua; 63 | } 64 | 65 | .wysiwyg-color-orange { 66 | color: orange; 67 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Toru KAWAMURA 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /app/assets/javascripts/bootstrap-editable-rails.js.coffee: -------------------------------------------------------------------------------- 1 | # bootstrap-editable-rails.js.coffee 2 | # Modify parameters of X-editable suitable for Rails. 3 | 4 | jQuery ($) -> 5 | EditableForm = $.fn.editableform.Constructor 6 | unless EditableForm::saveWithUrlHook? 7 | EditableForm::saveWithUrlHook = (value) -> 8 | originalUrl = @options.url 9 | resource = @options.resource 10 | @options.url = (params) => 11 | # TODO: should not send when create new object 12 | if typeof originalUrl == 'function' # user's function 13 | originalUrl.call(@options.scope, params) 14 | else if originalUrl? && @options.send != 'never' 15 | # send ajax to server and return deferred object 16 | obj = {} 17 | obj[params.name] = params.value 18 | # support custom inputtypes (eg address) 19 | if resource 20 | params[resource] = obj 21 | else 22 | params = obj 23 | delete params.name 24 | delete params.value 25 | delete params.pk 26 | $.ajax($.extend({ 27 | url : originalUrl 28 | data : params 29 | type : 'PUT' # TODO: should be 'POST' when create new object 30 | dataType: 'json' 31 | }, @options.ajaxOptions)) 32 | @saveWithoutUrlHook(value) 33 | EditableForm::saveWithoutUrlHook = EditableForm::save 34 | EditableForm::save = EditableForm::saveWithUrlHook 35 | -------------------------------------------------------------------------------- /app/assets/stylesheets/inputs-ext/typeaheadjs/lib/typeahead.js-bootstrap.css: -------------------------------------------------------------------------------- 1 | .twitter-typeahead .tt-query, 2 | .twitter-typeahead .tt-hint { 3 | margin-bottom: 0; 4 | } 5 | 6 | .tt-dropdown-menu { 7 | min-width: 160px; 8 | margin-top: 2px; 9 | padding: 5px 0; 10 | background-color: #fff; 11 | border: 1px solid #ccc; 12 | border: 1px solid rgba(0,0,0,.2); 13 | *border-right-width: 2px; 14 | *border-bottom-width: 2px; 15 | -webkit-border-radius: 6px; 16 | -moz-border-radius: 6px; 17 | border-radius: 6px; 18 | -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); 19 | -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); 20 | box-shadow: 0 5px 10px rgba(0,0,0,.2); 21 | -webkit-background-clip: padding-box; 22 | -moz-background-clip: padding; 23 | background-clip: padding-box; 24 | } 25 | 26 | .tt-suggestion { 27 | display: block; 28 | padding: 3px 20px; 29 | } 30 | 31 | .tt-suggestion.tt-is-under-cursor { 32 | color: #fff; 33 | background-color: #0081c2; 34 | background-image: -moz-linear-gradient(top, #0088cc, #0077b3); 35 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); 36 | background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); 37 | background-image: -o-linear-gradient(top, #0088cc, #0077b3); 38 | background-image: linear-gradient(to bottom, #0088cc, #0077b3); 39 | background-repeat: repeat-x; 40 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0) 41 | } 42 | 43 | .tt-suggestion.tt-is-under-cursor a { 44 | color: #fff; 45 | } 46 | 47 | .tt-suggestion p { 48 | margin: 0; 49 | } 50 | -------------------------------------------------------------------------------- /app/assets/javascripts/inputs-ext/typeaheadjs/typeaheadjs.js: -------------------------------------------------------------------------------- 1 | /** 2 | Typeahead.js input, based on [Twitter Typeahead](http://twitter.github.io/typeahead.js). 3 | It is mainly replacement of typeahead in Bootstrap 3. 4 | 5 | 6 | @class typeaheadjs 7 | @extends text 8 | @since 1.5.0 9 | @final 10 | @example 11 | 12 | 30 | **/ 31 | (function ($) { 32 | "use strict"; 33 | 34 | var Constructor = function (options) { 35 | this.init('typeaheadjs', options, Constructor.defaults); 36 | }; 37 | 38 | $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.text); 39 | 40 | $.extend(Constructor.prototype, { 41 | render: function() { 42 | this.renderClear(); 43 | this.setClass(); 44 | this.setAttr('placeholder'); 45 | this.$input.typeahead(this.options.typeahead); 46 | 47 | // copy `input-sm | input-lg` classes to placeholder input 48 | if($.fn.editableform.engine === 'bs3') { 49 | if(this.$input.hasClass('input-sm')) { 50 | this.$input.siblings('input.tt-hint').addClass('input-sm'); 51 | } 52 | if(this.$input.hasClass('input-lg')) { 53 | this.$input.siblings('input.tt-hint').addClass('input-lg'); 54 | } 55 | } 56 | } 57 | }); 58 | 59 | Constructor.defaults = $.extend({}, $.fn.editabletypes.list.defaults, { 60 | /** 61 | @property tpl 62 | @default 63 | **/ 64 | tpl:'', 65 | /** 66 | Configuration of typeahead itself. 67 | [Full list of options](https://github.com/twitter/typeahead.js#dataset). 68 | 69 | @property typeahead 70 | @type object 71 | @default null 72 | **/ 73 | typeahead: null, 74 | /** 75 | Whether to show `clear` button 76 | 77 | @property clear 78 | @type boolean 79 | @default true 80 | **/ 81 | clear: true 82 | }); 83 | 84 | $.fn.editabletypes.typeaheadjs = Constructor; 85 | 86 | }(window.jQuery)); -------------------------------------------------------------------------------- /app/assets/stylesheets/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.css: -------------------------------------------------------------------------------- 1 | ul.wysihtml5-toolbar { 2 | margin: 0; 3 | padding: 0; 4 | display: block; 5 | } 6 | 7 | ul.wysihtml5-toolbar::after { 8 | clear: both; 9 | display: table; 10 | content: ""; 11 | } 12 | 13 | ul.wysihtml5-toolbar > li { 14 | float: left; 15 | display: list-item; 16 | list-style: none; 17 | margin: 0 5px 10px 0; 18 | } 19 | 20 | ul.wysihtml5-toolbar a[data-wysihtml5-command=bold] { 21 | font-weight: bold; 22 | } 23 | 24 | ul.wysihtml5-toolbar a[data-wysihtml5-command=italic] { 25 | font-style: italic; 26 | } 27 | 28 | ul.wysihtml5-toolbar a[data-wysihtml5-command=underline] { 29 | text-decoration: underline; 30 | } 31 | 32 | ul.wysihtml5-toolbar a.btn.wysihtml5-command-active { 33 | background-image: none; 34 | -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); 35 | -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); 36 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); 37 | background-color: #E6E6E6; 38 | background-color: #D9D9D9; 39 | outline: 0; 40 | } 41 | 42 | ul.wysihtml5-commands-disabled .dropdown-menu { 43 | display: none !important; 44 | } 45 | 46 | ul.wysihtml5-toolbar div.wysihtml5-colors { 47 | display:block; 48 | width: 50px; 49 | height: 20px; 50 | margin-top: 2px; 51 | margin-left: 5px; 52 | position: absolute; 53 | pointer-events: none; 54 | } 55 | 56 | ul.wysihtml5-toolbar a.wysihtml5-colors-title { 57 | padding-left: 70px; 58 | } 59 | 60 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="black"] { 61 | background: black !important; 62 | } 63 | 64 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="silver"] { 65 | background: silver !important; 66 | } 67 | 68 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="gray"] { 69 | background: gray !important; 70 | } 71 | 72 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="maroon"] { 73 | background: maroon !important; 74 | } 75 | 76 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="red"] { 77 | background: red !important; 78 | } 79 | 80 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="purple"] { 81 | background: purple !important; 82 | } 83 | 84 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="green"] { 85 | background: green !important; 86 | } 87 | 88 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="olive"] { 89 | background: olive !important; 90 | } 91 | 92 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="navy"] { 93 | background: navy !important; 94 | } 95 | 96 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="blue"] { 97 | background: blue !important; 98 | } 99 | 100 | ul.wysihtml5-toolbar div[data-wysihtml5-command-value="orange"] { 101 | background: orange !important; 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bootstrap Editable Rails 2 | 3 | In-place editing with Twitter Bootstrap for Rails 3/4 4 | 5 | This gem is based on X-editable (v1.5.1) which is the new version of Bootstrap Editable. 6 | 7 | https://github.com/vitalets/x-editable 8 | 9 | ## Demo & Documents 10 | 11 | See http://vitalets.github.com/x-editable 12 | 13 | ## Installation 14 | 15 | Add this line to your application's Gemfile: 16 | 17 | gem 'bootstrap-editable-rails' 18 | 19 | And then execute: 20 | 21 | $ bundle 22 | 23 | ## Usage 24 | 25 | ### JavaScript & Stylesheet 26 | 27 | Write the top of `app/assets/javascripts/application.js` like this: 28 | 29 | ```javascript 30 | //= require jquery 31 | //= require jquery_ujs 32 | //= require bootstrap 33 | //= require bootstrap-editable 34 | //= require bootstrap-editable-rails 35 | //= require_tree . 36 | ``` 37 | 38 | and need to load `bootstrap-editable.css` at the place where you like. 39 | 40 | ### HTML 41 | 42 | Follow the documents of X-editable above. 43 | 44 | Additional required attribute is `resource`. 45 | 46 | ```html 47 | superuser 48 | ``` 49 | 50 | then, sends `PUT /posts/1` request with the body: 51 | 52 | ``` 53 | post[username]=superuser 54 | ``` 55 | 56 | When using `textarea` type, `textarea_format` helper method for formatting line breaks is available. 57 | 58 | ```html 59 | 60 | <%= textarea_format(@post.body) %> 61 | 62 | ``` 63 | 64 | ### Controller 65 | 66 | PostsController receives the parameters 67 | 68 | ``` 69 | { "id" => "1", "post" => { "username" => "superuser" } } 70 | ``` 71 | 72 | and must respond with 2xx (means _success_) status code if successful. 73 | 74 | For example, scaffold works well by 204 because default dataType is json. 75 | 76 | ```ruby 77 | def update 78 | @post = Post.find(params[:id]) 79 | 80 | respond_to do |format| 81 | if @post.update_attributes(params[:post]) 82 | format.html { redirect_to @post, notice: 'Post was successfully updated.' } 83 | format.json { head :no_content } # 204 No Content 84 | else 85 | format.html { render action: "edit" } 86 | format.json { render json: @post.errors, status: :unprocessable_entity } 87 | end 88 | end 89 | end 90 | ``` 91 | 92 | #### Note 93 | 94 | The scaffold above will not work with jQuery 1.9.0 (included in jquery-rails 2.2.0) because of jQuery's bug. 95 | 96 | https://github.com/jquery/jquery/pull/1142 97 | 98 | If you use the old version of jQuery, please update jquery-rails to avoid it. 99 | 100 | 101 | ## Contributing 102 | 103 | 1. Fork it 104 | 2. Create your feature branch (`git checkout -b my-new-feature`) 105 | 3. Commit your changes (`git commit -am 'Add some feature'`) 106 | 4. Push to the branch (`git push origin my-new-feature`) 107 | 5. Create new Pull Request 108 | -------------------------------------------------------------------------------- /app/assets/javascripts/inputs-ext/wysihtml5/wysihtml5.js: -------------------------------------------------------------------------------- 1 | /** 2 | Bootstrap wysihtml5 editor. Based on [bootstrap-wysihtml5](https://github.com/jhollingworth/bootstrap-wysihtml5). 3 | You should include **manually** distributives of `wysihtml5` and `bootstrap-wysihtml5`: 4 | 5 | 6 | 7 | 8 | 9 | And also include `wysihtml5.js` from `inputs-ext` directory of x-editable: 10 | 11 | 12 | 13 | **Note:** It's better to use fresh bootstrap-wysihtml5 from it's [master branch](https://github.com/jhollingworth/bootstrap-wysihtml5/tree/master/src) as there is update for correct image insertion. 14 | 15 | @class wysihtml5 16 | @extends abstractinput 17 | @final 18 | @since 1.4.0 19 | @example 20 |

awesome

comment!
21 | 29 | **/ 30 | (function ($) { 31 | "use strict"; 32 | 33 | var Wysihtml5 = function (options) { 34 | this.init('wysihtml5', options, Wysihtml5.defaults); 35 | 36 | //extend wysihtml5 manually as $.extend not recursive 37 | this.options.wysihtml5 = $.extend({}, Wysihtml5.defaults.wysihtml5, options.wysihtml5); 38 | }; 39 | 40 | $.fn.editableutils.inherit(Wysihtml5, $.fn.editabletypes.abstractinput); 41 | 42 | $.extend(Wysihtml5.prototype, { 43 | render: function () { 44 | var deferred = $.Deferred(), 45 | msieOld; 46 | 47 | //generate unique id as it required for wysihtml5 48 | this.$input.attr('id', 'textarea_'+(new Date()).getTime()); 49 | 50 | this.setClass(); 51 | this.setAttr('placeholder'); 52 | 53 | //resolve deffered when widget loaded 54 | $.extend(this.options.wysihtml5, { 55 | events: { 56 | load: function() { 57 | deferred.resolve(); 58 | } 59 | } 60 | }); 61 | 62 | this.$input.wysihtml5(this.options.wysihtml5); 63 | 64 | /* 65 | In IE8 wysihtml5 iframe stays on the same line with buttons toolbar (inside popover). 66 | The only solution I found is to add
. If you fine better way, please send PR. 67 | */ 68 | msieOld = /msie\s*(8|7|6)/.test(navigator.userAgent.toLowerCase()); 69 | if(msieOld) { 70 | this.$input.before('

'); 71 | } 72 | 73 | return deferred.promise(); 74 | }, 75 | 76 | value2html: function(value, element) { 77 | $(element).html(value); 78 | }, 79 | 80 | html2value: function(html) { 81 | return html; 82 | }, 83 | 84 | value2input: function(value) { 85 | this.$input.data("wysihtml5").editor.setValue(value, true); 86 | }, 87 | 88 | activate: function() { 89 | this.$input.data("wysihtml5").editor.focus(); 90 | }, 91 | 92 | isEmpty: function($element) { 93 | if($.trim($element.html()) === '') { 94 | return true; 95 | } else if($.trim($element.text()) !== '') { 96 | return false; 97 | } else { 98 | //e.g. '', '
', '

' 99 | return !$element.height() || !$element.width(); 100 | } 101 | } 102 | }); 103 | 104 | Wysihtml5.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { 105 | /** 106 | @property tpl 107 | @default 108 | **/ 109 | tpl:'', 110 | /** 111 | @property inputclass 112 | @default editable-wysihtml5 113 | **/ 114 | inputclass: 'editable-wysihtml5', 115 | /** 116 | Placeholder attribute of input. Shown when input is empty. 117 | 118 | @property placeholder 119 | @type string 120 | @default null 121 | **/ 122 | placeholder: null, 123 | /** 124 | Wysihtml5 default options. 125 | See https://github.com/jhollingworth/bootstrap-wysihtml5#options 126 | 127 | @property wysihtml5 128 | @type object 129 | @default {stylesheets: false} 130 | **/ 131 | wysihtml5: { 132 | stylesheets: false //see https://github.com/jhollingworth/bootstrap-wysihtml5/issues/183 133 | } 134 | }); 135 | 136 | $.fn.editabletypes.wysihtml5 = Wysihtml5; 137 | 138 | }(window.jQuery)); 139 | -------------------------------------------------------------------------------- /app/assets/javascripts/inputs-ext/address/address.js: -------------------------------------------------------------------------------- 1 | /** 2 | Address editable input. 3 | Internally value stored as {city: "Moscow", street: "Lenina", building: "15"} 4 | 5 | @class address 6 | @extends abstractinput 7 | @final 8 | @example 9 | awesome 10 | 23 | **/ 24 | (function ($) { 25 | "use strict"; 26 | 27 | var Address = function (options) { 28 | this.init('address', options, Address.defaults); 29 | }; 30 | 31 | //inherit from Abstract input 32 | $.fn.editableutils.inherit(Address, $.fn.editabletypes.abstractinput); 33 | 34 | $.extend(Address.prototype, { 35 | /** 36 | Renders input from tpl 37 | 38 | @method render() 39 | **/ 40 | render: function() { 41 | this.$input = this.$tpl.find('input'); 42 | }, 43 | 44 | /** 45 | Default method to show value in element. Can be overwritten by display option. 46 | 47 | @method value2html(value, element) 48 | **/ 49 | value2html: function(value, element) { 50 | if(!value) { 51 | $(element).empty(); 52 | return; 53 | } 54 | var html = $('
').text(value.city).html() + ', ' + $('
').text(value.street).html() + ' st., bld. ' + $('
').text(value.building).html(); 55 | $(element).html(html); 56 | }, 57 | 58 | /** 59 | Gets value from element's html 60 | 61 | @method html2value(html) 62 | **/ 63 | html2value: function(html) { 64 | /* 65 | you may write parsing method to get value by element's html 66 | e.g. "Moscow, st. Lenina, bld. 15" => {city: "Moscow", street: "Lenina", building: "15"} 67 | but for complex structures it's not recommended. 68 | Better set value directly via javascript, e.g. 69 | editable({ 70 | value: { 71 | city: "Moscow", 72 | street: "Lenina", 73 | building: "15" 74 | } 75 | }); 76 | */ 77 | return null; 78 | }, 79 | 80 | /** 81 | Converts value to string. 82 | It is used in internal comparing (not for sending to server). 83 | 84 | @method value2str(value) 85 | **/ 86 | value2str: function(value) { 87 | var str = ''; 88 | if(value) { 89 | for(var k in value) { 90 | str = str + k + ':' + value[k] + ';'; 91 | } 92 | } 93 | return str; 94 | }, 95 | 96 | /* 97 | Converts string to value. Used for reading value from 'data-value' attribute. 98 | 99 | @method str2value(str) 100 | */ 101 | str2value: function(str) { 102 | /* 103 | this is mainly for parsing value defined in data-value attribute. 104 | If you will always set value by javascript, no need to overwrite it 105 | */ 106 | return str; 107 | }, 108 | 109 | /** 110 | Sets value of input. 111 | 112 | @method value2input(value) 113 | @param {mixed} value 114 | **/ 115 | value2input: function(value) { 116 | if(!value) { 117 | return; 118 | } 119 | this.$input.filter('[name="city"]').val(value.city); 120 | this.$input.filter('[name="street"]').val(value.street); 121 | this.$input.filter('[name="building"]').val(value.building); 122 | }, 123 | 124 | /** 125 | Returns value of input. 126 | 127 | @method input2value() 128 | **/ 129 | input2value: function() { 130 | return { 131 | city: this.$input.filter('[name="city"]').val(), 132 | street: this.$input.filter('[name="street"]').val(), 133 | building: this.$input.filter('[name="building"]').val() 134 | }; 135 | }, 136 | 137 | /** 138 | Activates input: sets focus on the first field. 139 | 140 | @method activate() 141 | **/ 142 | activate: function() { 143 | this.$input.filter('[name="city"]').focus(); 144 | }, 145 | 146 | /** 147 | Attaches handler to submit form in case of 'showbuttons=false' mode 148 | 149 | @method autosubmit() 150 | **/ 151 | autosubmit: function() { 152 | this.$input.keydown(function (e) { 153 | if (e.which === 13) { 154 | $(this).closest('form').submit(); 155 | } 156 | }); 157 | } 158 | }); 159 | 160 | Address.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { 161 | tpl: '
'+ 162 | '
'+ 163 | '
', 164 | 165 | inputclass: '' 166 | }); 167 | 168 | $.fn.editabletypes.address = Address; 169 | 170 | }(window.jQuery)); -------------------------------------------------------------------------------- /app/assets/stylesheets/bootstrap-editable.scss: -------------------------------------------------------------------------------- 1 | /*! X-editable - v1.5.1 2 | * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery 3 | * http://github.com/vitalets/x-editable 4 | * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ 5 | .editableform { 6 | margin-bottom: 0; /* overwrites bootstrap margin */ 7 | } 8 | 9 | .editableform .control-group { 10 | margin-bottom: 0; /* overwrites bootstrap margin */ 11 | white-space: nowrap; /* prevent wrapping buttons on new line */ 12 | line-height: 20px; /* overwriting bootstrap line-height. See #133 */ 13 | } 14 | 15 | /* 16 | BS3 width:1005 for inputs breaks editable form in popup 17 | See: https://github.com/vitalets/x-editable/issues/393 18 | */ 19 | .editableform .form-control { 20 | width: auto; 21 | } 22 | 23 | .editable-buttons { 24 | display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ 25 | vertical-align: top; 26 | margin-left: 7px; 27 | /* inline-block emulation for IE7*/ 28 | zoom: 1; 29 | *display: inline; 30 | } 31 | 32 | .editable-buttons.editable-buttons-bottom { 33 | display: block; 34 | margin-top: 7px; 35 | margin-left: 0; 36 | } 37 | 38 | .editable-input { 39 | vertical-align: top; 40 | display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ 41 | width: auto; /* bootstrap-responsive has width: 100% that breakes layout */ 42 | white-space: normal; /* reset white-space decalred in parent*/ 43 | /* display-inline emulation for IE7*/ 44 | zoom: 1; 45 | *display: inline; 46 | } 47 | 48 | .editable-buttons .editable-cancel { 49 | margin-left: 7px; 50 | } 51 | 52 | /*for jquery-ui buttons need set height to look more pretty*/ 53 | .editable-buttons button.ui-button-icon-only { 54 | height: 24px; 55 | width: 30px; 56 | } 57 | 58 | .editableform-loading { 59 | background: image-url("bootstrap-editable/loading.gif") center center no-repeat; 60 | height: 25px; 61 | width: auto; 62 | min-width: 25px; 63 | } 64 | 65 | .editable-inline .editableform-loading { 66 | background-position: left 5px; 67 | } 68 | 69 | .editable-error-block { 70 | max-width: 300px; 71 | margin: 5px 0 0 0; 72 | width: auto; 73 | white-space: normal; 74 | } 75 | 76 | /*add padding for jquery ui*/ 77 | .editable-error-block.ui-state-error { 78 | padding: 3px; 79 | } 80 | 81 | .editable-error { 82 | color: red; 83 | } 84 | 85 | /* ---- For specific types ---- */ 86 | 87 | .editableform .editable-date { 88 | padding: 0; 89 | margin: 0; 90 | float: left; 91 | } 92 | 93 | /* move datepicker icon to center of add-on button. See https://github.com/vitalets/x-editable/issues/183 */ 94 | .editable-inline .add-on .icon-th { 95 | margin-top: 3px; 96 | margin-left: 1px; 97 | } 98 | 99 | 100 | /* checklist vertical alignment */ 101 | .editable-checklist label input[type="checkbox"], 102 | .editable-checklist label span { 103 | vertical-align: middle; 104 | margin: 0; 105 | } 106 | 107 | .editable-checklist label { 108 | white-space: nowrap; 109 | } 110 | 111 | /* set exact width of textarea to fit buttons toolbar */ 112 | .editable-wysihtml5 { 113 | width: 566px; 114 | height: 250px; 115 | } 116 | 117 | /* clear button shown as link in date inputs */ 118 | .editable-clear { 119 | clear: both; 120 | font-size: 0.9em; 121 | text-decoration: none; 122 | text-align: right; 123 | } 124 | 125 | /* IOS-style clear button for text inputs */ 126 | .editable-clear-x { 127 | background: image-url("bootstrap-editable/clear.png") center center no-repeat; 128 | display: block; 129 | width: 13px; 130 | height: 13px; 131 | position: absolute; 132 | opacity: 0.6; 133 | z-index: 100; 134 | 135 | top: 50%; 136 | right: 6px; 137 | margin-top: -6px; 138 | 139 | } 140 | 141 | .editable-clear-x:hover { 142 | opacity: 1; 143 | } 144 | 145 | .editable-pre-wrapped { 146 | white-space: pre-wrap; 147 | } 148 | .editable-container.editable-popup { 149 | max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ 150 | } 151 | 152 | .editable-container.popover { 153 | width: auto; /* without this rule popover does not stretch */ 154 | } 155 | 156 | .editable-container.editable-inline { 157 | display: inline-block; 158 | vertical-align: middle; 159 | width: auto; 160 | /* inline-block emulation for IE7*/ 161 | zoom: 1; 162 | *display: inline; 163 | } 164 | 165 | .editable-container.ui-widget { 166 | font-size: inherit; /* jqueryui widget font 1.1em too big, overwrite it */ 167 | z-index: 9990; /* should be less than select2 dropdown z-index to close dropdown first when click */ 168 | } 169 | .editable-click, 170 | a.editable-click, 171 | a.editable-click:hover { 172 | text-decoration: none; 173 | border-bottom: dashed 1px #0088cc; 174 | } 175 | 176 | .editable-click.editable-disabled, 177 | a.editable-click.editable-disabled, 178 | a.editable-click.editable-disabled:hover { 179 | color: #585858; 180 | cursor: default; 181 | border-bottom: none; 182 | } 183 | 184 | .editable-empty, .editable-empty:hover, .editable-empty:focus{ 185 | font-style: italic; 186 | color: #DD1144; 187 | /* border-bottom: none; */ 188 | text-decoration: none; 189 | } 190 | 191 | .editable-unsaved { 192 | font-weight: bold; 193 | } 194 | 195 | .editable-unsaved:after { 196 | /* content: '*'*/ 197 | } 198 | 199 | .editable-bg-transition { 200 | -webkit-transition: background-color 1400ms ease-out; 201 | -moz-transition: background-color 1400ms ease-out; 202 | -o-transition: background-color 1400ms ease-out; 203 | -ms-transition: background-color 1400ms ease-out; 204 | transition: background-color 1400ms ease-out; 205 | } 206 | 207 | /*see https://github.com/vitalets/x-editable/issues/139 */ 208 | .form-horizontal .editable 209 | { 210 | padding-top: 5px; 211 | display:inline-block; 212 | } 213 | 214 | /* Removed datepicker css. If you want to use datepicker, add latest datepicker library such as bootstrap-datepicker. */ 215 | -------------------------------------------------------------------------------- /app/assets/javascripts/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.min.js: -------------------------------------------------------------------------------- 1 | !function($,wysi){"use strict";var tpl={"font-styles":function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return""},emphasis:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return"
  • "+""+"
  • "},lists:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return"
  • "+"
    "+""+""+""+""+"
    "+"
  • "},link:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return"
  • "+""+""+"
  • "},image:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return"
  • "+""+""+"
  • "},html:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return"
  • "+"
    "+""+"
    "+"
  • "},color:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return""}};var templates=function(key,locale,options){return tpl[key](locale,options)};var Wysihtml5=function(el,options){this.el=el;var toolbarOpts=options||defaultOptions;for(var t in toolbarOpts.customTemplates){tpl[t]=toolbarOpts.customTemplates[t]}this.toolbar=this.createToolbar(el,toolbarOpts);this.editor=this.createEditor(options);window.editor=this.editor;$("iframe.wysihtml5-sandbox").each(function(i,el){$(el.contentWindow).off("focus.wysihtml5").on({"focus.wysihtml5":function(){$("li.dropdown").removeClass("open")}})})};Wysihtml5.prototype={constructor:Wysihtml5,createEditor:function(options){options=options||{};options=$.extend(true,{},options);options.toolbar=this.toolbar[0];var editor=new wysi.Editor(this.el[0],options);if(options&&options.events){for(var eventName in options.events){editor.on(eventName,options.events[eventName])}}return editor},createToolbar:function(el,options){var self=this;var toolbar=$("
      ",{"class":"wysihtml5-toolbar",style:"display:none"});var culture=options.locale||defaultOptions.locale||"en";for(var key in defaultOptions){var value=false;if(options[key]!==undefined){if(options[key]===true){value=true}}else{value=defaultOptions[key]}if(value===true){toolbar.append(templates(key,locale[culture],options));if(key==="html"){this.initHtml(toolbar)}if(key==="link"){this.initInsertLink(toolbar)}if(key==="image"){this.initInsertImage(toolbar)}}}if(options.toolbar){for(key in options.toolbar){toolbar.append(options.toolbar[key])}}toolbar.find("a[data-wysihtml5-command='formatBlock']").click(function(e){var target=e.target||e.srcElement;var el=$(target);self.toolbar.find(".current-font").text(el.html())});toolbar.find("a[data-wysihtml5-command='foreColor']").click(function(e){var target=e.target||e.srcElement;var el=$(target);self.toolbar.find(".current-color").text(el.html())});this.el.before(toolbar);return toolbar},initHtml:function(toolbar){var changeViewSelector="a[data-wysihtml5-action='change_view']";toolbar.find(changeViewSelector).click(function(e){toolbar.find("a.btn").not(changeViewSelector).toggleClass("disabled")})},initInsertImage:function(toolbar){var self=this;var insertImageModal=toolbar.find(".bootstrap-wysihtml5-insert-image-modal");var urlInput=insertImageModal.find(".bootstrap-wysihtml5-insert-image-url");var insertButton=insertImageModal.find("a.btn-primary");var initialValue=urlInput.val();var caretBookmark;var insertImage=function(){var url=urlInput.val();urlInput.val(initialValue);self.editor.currentView.element.focus();if(caretBookmark){self.editor.composer.selection.setBookmark(caretBookmark);caretBookmark=null}self.editor.composer.commands.exec("insertImage",url)};urlInput.keypress(function(e){if(e.which==13){insertImage();insertImageModal.modal("hide")}});insertButton.click(insertImage);insertImageModal.on("shown",function(){urlInput.focus()});insertImageModal.on("hide",function(){self.editor.currentView.element.focus()});toolbar.find("a[data-wysihtml5-command=insertImage]").click(function(){var activeButton=$(this).hasClass("wysihtml5-command-active");if(!activeButton){self.editor.currentView.element.focus(false);caretBookmark=self.editor.composer.selection.getBookmark();insertImageModal.appendTo("body").modal("show");insertImageModal.on("click.dismiss.modal",'[data-dismiss="modal"]',function(e){e.stopPropagation()});return false}else{return true}})},initInsertLink:function(toolbar){var self=this;var insertLinkModal=toolbar.find(".bootstrap-wysihtml5-insert-link-modal");var urlInput=insertLinkModal.find(".bootstrap-wysihtml5-insert-link-url");var insertButton=insertLinkModal.find("a.btn-primary");var initialValue=urlInput.val();var caretBookmark;var insertLink=function(){var url=urlInput.val();urlInput.val(initialValue);self.editor.currentView.element.focus();if(caretBookmark){self.editor.composer.selection.setBookmark(caretBookmark);caretBookmark=null}self.editor.composer.commands.exec("createLink",{href:url,target:"_blank",rel:"nofollow"})};var pressedEnter=false;urlInput.keypress(function(e){if(e.which==13){insertLink();insertLinkModal.modal("hide")}});insertButton.click(insertLink);insertLinkModal.on("shown",function(){urlInput.focus()});insertLinkModal.on("hide",function(){self.editor.currentView.element.focus()});toolbar.find("a[data-wysihtml5-command=createLink]").click(function(){var activeButton=$(this).hasClass("wysihtml5-command-active");if(!activeButton){self.editor.currentView.element.focus(false);caretBookmark=self.editor.composer.selection.getBookmark();insertLinkModal.appendTo("body").modal("show");insertLinkModal.on("click.dismiss.modal",'[data-dismiss="modal"]',function(e){e.stopPropagation()});return false}else{return true}})}};var methods={resetDefaults:function(){$.fn.wysihtml5.defaultOptions=$.extend(true,{},$.fn.wysihtml5.defaultOptionsCache)},bypassDefaults:function(options){return this.each(function(){var $this=$(this);$this.data("wysihtml5",new Wysihtml5($this,options))})},shallowExtend:function(options){var settings=$.extend({},$.fn.wysihtml5.defaultOptions,options||{});var that=this;return methods.bypassDefaults.apply(that,[settings])},deepExtend:function(options){var settings=$.extend(true,{},$.fn.wysihtml5.defaultOptions,options||{});var that=this;return methods.bypassDefaults.apply(that,[settings])},init:function(options){var that=this;return methods.shallowExtend.apply(that,[options])}};$.fn.wysihtml5=function(method){if(methods[method]){return methods[method].apply(this,Array.prototype.slice.call(arguments,1))}else if(typeof method==="object"||!method){return methods.init.apply(this,arguments)}else{$.error("Method "+method+" does not exist on jQuery.wysihtml5")}};$.fn.wysihtml5.Constructor=Wysihtml5;var defaultOptions=$.fn.wysihtml5.defaultOptions={"font-styles":true,color:false,emphasis:true,lists:true,html:false,link:true,image:true,events:{},parserRules:{classes:{"wysiwyg-color-silver":1,"wysiwyg-color-gray":1,"wysiwyg-color-white":1,"wysiwyg-color-maroon":1,"wysiwyg-color-red":1,"wysiwyg-color-purple":1,"wysiwyg-color-fuchsia":1,"wysiwyg-color-green":1,"wysiwyg-color-lime":1,"wysiwyg-color-olive":1,"wysiwyg-color-yellow":1,"wysiwyg-color-navy":1,"wysiwyg-color-blue":1,"wysiwyg-color-teal":1,"wysiwyg-color-aqua":1,"wysiwyg-color-orange":1},tags:{b:{},i:{},br:{},ol:{},ul:{},li:{},h1:{},h2:{},h3:{},blockquote:{},u:1,img:{check_attributes:{width:"numbers",alt:"alt",src:"url",height:"numbers"}},a:{set_attributes:{target:"_blank",rel:"nofollow"},check_attributes:{href:"url"}},span:1,div:1,code:1,pre:1}},stylesheets:["./lib/css/wysiwyg-color.css"],locale:"en"};if(typeof $.fn.wysihtml5.defaultOptionsCache==="undefined"){$.fn.wysihtml5.defaultOptionsCache=$.extend(true,{},$.fn.wysihtml5.defaultOptions)}var locale=$.fn.wysihtml5.locale={en:{font_styles:{normal:"Normal text",h1:"Heading 1",h2:"Heading 2",h3:"Heading 3"},emphasis:{bold:"Bold",italic:"Italic",underline:"Underline"},lists:{unordered:"Unordered list",ordered:"Ordered list",outdent:"Outdent",indent:"Indent"},link:{insert:"Insert link",cancel:"Cancel"},image:{insert:"Insert image",cancel:"Cancel"},html:{edit:"Edit HTML"},colours:{black:"Black",silver:"Silver",gray:"Grey",maroon:"Maroon",red:"Red",purple:"Purple",green:"Green",olive:"Olive",navy:"Navy",blue:"Blue",orange:"Orange"}}}}(window.jQuery,window.wysihtml5); -------------------------------------------------------------------------------- /app/assets/javascripts/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.js: -------------------------------------------------------------------------------- 1 | !function($, wysi) { 2 | "use strict"; 3 | 4 | var tpl = { 5 | "font-styles": function(locale, options) { 6 | var size = (options && options.size) ? ' btn-'+options.size : ''; 7 | return ""; 18 | }, 19 | 20 | "emphasis": function(locale, options) { 21 | var size = (options && options.size) ? ' btn-'+options.size : ''; 22 | return "
    • " + 23 | "" + 28 | "
    • "; 29 | }, 30 | 31 | "lists": function(locale, options) { 32 | var size = (options && options.size) ? ' btn-'+options.size : ''; 33 | return "
    • " + 34 | "
      " + 35 | "" + 36 | "" + 37 | "" + 38 | "" + 39 | "
      " + 40 | "
    • "; 41 | }, 42 | 43 | "link": function(locale, options) { 44 | var size = (options && options.size) ? ' btn-'+options.size : ''; 45 | return "
    • " + 46 | "" + 59 | "" + 60 | "
    • "; 61 | }, 62 | 63 | "image": function(locale, options) { 64 | var size = (options && options.size) ? ' btn-'+options.size : ''; 65 | return "
    • " + 66 | "" + 79 | "" + 80 | "
    • "; 81 | }, 82 | 83 | "html": function(locale, options) { 84 | var size = (options && options.size) ? ' btn-'+options.size : ''; 85 | return "
    • " + 86 | "
      " + 87 | "" + 88 | "
      " + 89 | "
    • "; 90 | }, 91 | 92 | "color": function(locale, options) { 93 | var size = (options && options.size) ? ' btn-'+options.size : ''; 94 | return ""; 112 | } 113 | }; 114 | 115 | var templates = function(key, locale, options) { 116 | return tpl[key](locale, options); 117 | }; 118 | 119 | 120 | var Wysihtml5 = function(el, options) { 121 | this.el = el; 122 | var toolbarOpts = options || defaultOptions; 123 | for(var t in toolbarOpts.customTemplates) { 124 | tpl[t] = toolbarOpts.customTemplates[t]; 125 | } 126 | this.toolbar = this.createToolbar(el, toolbarOpts); 127 | this.editor = this.createEditor(options); 128 | 129 | window.editor = this.editor; 130 | 131 | $('iframe.wysihtml5-sandbox').each(function(i, el){ 132 | $(el.contentWindow).off('focus.wysihtml5').on({ 133 | 'focus.wysihtml5' : function(){ 134 | $('li.dropdown').removeClass('open'); 135 | } 136 | }); 137 | }); 138 | }; 139 | 140 | Wysihtml5.prototype = { 141 | 142 | constructor: Wysihtml5, 143 | 144 | createEditor: function(options) { 145 | options = options || {}; 146 | 147 | // Add the toolbar to a clone of the options object so multiple instances 148 | // of the WYISYWG don't break because "toolbar" is already defined 149 | options = $.extend(true, {}, options); 150 | options.toolbar = this.toolbar[0]; 151 | 152 | var editor = new wysi.Editor(this.el[0], options); 153 | 154 | if(options && options.events) { 155 | for(var eventName in options.events) { 156 | editor.on(eventName, options.events[eventName]); 157 | } 158 | } 159 | return editor; 160 | }, 161 | 162 | createToolbar: function(el, options) { 163 | var self = this; 164 | var toolbar = $("
        ", { 165 | 'class' : "wysihtml5-toolbar", 166 | 'style': "display:none" 167 | }); 168 | var culture = options.locale || defaultOptions.locale || "en"; 169 | for(var key in defaultOptions) { 170 | var value = false; 171 | 172 | if(options[key] !== undefined) { 173 | if(options[key] === true) { 174 | value = true; 175 | } 176 | } else { 177 | value = defaultOptions[key]; 178 | } 179 | 180 | if(value === true) { 181 | toolbar.append(templates(key, locale[culture], options)); 182 | 183 | if(key === "html") { 184 | this.initHtml(toolbar); 185 | } 186 | 187 | if(key === "link") { 188 | this.initInsertLink(toolbar); 189 | } 190 | 191 | if(key === "image") { 192 | this.initInsertImage(toolbar); 193 | } 194 | } 195 | } 196 | 197 | if(options.toolbar) { 198 | for(key in options.toolbar) { 199 | toolbar.append(options.toolbar[key]); 200 | } 201 | } 202 | 203 | toolbar.find("a[data-wysihtml5-command='formatBlock']").click(function(e) { 204 | var target = e.target || e.srcElement; 205 | var el = $(target); 206 | self.toolbar.find('.current-font').text(el.html()); 207 | }); 208 | 209 | toolbar.find("a[data-wysihtml5-command='foreColor']").click(function(e) { 210 | var target = e.target || e.srcElement; 211 | var el = $(target); 212 | self.toolbar.find('.current-color').text(el.html()); 213 | }); 214 | 215 | this.el.before(toolbar); 216 | 217 | return toolbar; 218 | }, 219 | 220 | initHtml: function(toolbar) { 221 | var changeViewSelector = "a[data-wysihtml5-action='change_view']"; 222 | toolbar.find(changeViewSelector).click(function(e) { 223 | toolbar.find('a.btn').not(changeViewSelector).toggleClass('disabled'); 224 | }); 225 | }, 226 | 227 | initInsertImage: function(toolbar) { 228 | var self = this; 229 | var insertImageModal = toolbar.find('.bootstrap-wysihtml5-insert-image-modal'); 230 | var urlInput = insertImageModal.find('.bootstrap-wysihtml5-insert-image-url'); 231 | var insertButton = insertImageModal.find('a.btn-primary'); 232 | var initialValue = urlInput.val(); 233 | var caretBookmark; 234 | 235 | var insertImage = function() { 236 | var url = urlInput.val(); 237 | urlInput.val(initialValue); 238 | self.editor.currentView.element.focus(); 239 | if (caretBookmark) { 240 | self.editor.composer.selection.setBookmark(caretBookmark); 241 | caretBookmark = null; 242 | } 243 | self.editor.composer.commands.exec("insertImage", url); 244 | }; 245 | 246 | urlInput.keypress(function(e) { 247 | if(e.which == 13) { 248 | insertImage(); 249 | insertImageModal.modal('hide'); 250 | } 251 | }); 252 | 253 | insertButton.click(insertImage); 254 | 255 | insertImageModal.on('shown', function() { 256 | urlInput.focus(); 257 | }); 258 | 259 | insertImageModal.on('hide', function() { 260 | self.editor.currentView.element.focus(); 261 | }); 262 | 263 | toolbar.find('a[data-wysihtml5-command=insertImage]').click(function() { 264 | var activeButton = $(this).hasClass("wysihtml5-command-active"); 265 | 266 | if (!activeButton) { 267 | self.editor.currentView.element.focus(false); 268 | caretBookmark = self.editor.composer.selection.getBookmark(); 269 | insertImageModal.appendTo('body').modal('show'); 270 | insertImageModal.on('click.dismiss.modal', '[data-dismiss="modal"]', function(e) { 271 | e.stopPropagation(); 272 | }); 273 | return false; 274 | } 275 | else { 276 | return true; 277 | } 278 | }); 279 | }, 280 | 281 | initInsertLink: function(toolbar) { 282 | var self = this; 283 | var insertLinkModal = toolbar.find('.bootstrap-wysihtml5-insert-link-modal'); 284 | var urlInput = insertLinkModal.find('.bootstrap-wysihtml5-insert-link-url'); 285 | var insertButton = insertLinkModal.find('a.btn-primary'); 286 | var initialValue = urlInput.val(); 287 | var caretBookmark; 288 | 289 | var insertLink = function() { 290 | var url = urlInput.val(); 291 | urlInput.val(initialValue); 292 | self.editor.currentView.element.focus(); 293 | if (caretBookmark) { 294 | self.editor.composer.selection.setBookmark(caretBookmark); 295 | caretBookmark = null; 296 | } 297 | self.editor.composer.commands.exec("createLink", { 298 | href: url, 299 | target: "_blank", 300 | rel: "nofollow" 301 | }); 302 | }; 303 | var pressedEnter = false; 304 | 305 | urlInput.keypress(function(e) { 306 | if(e.which == 13) { 307 | insertLink(); 308 | insertLinkModal.modal('hide'); 309 | } 310 | }); 311 | 312 | insertButton.click(insertLink); 313 | 314 | insertLinkModal.on('shown', function() { 315 | urlInput.focus(); 316 | }); 317 | 318 | insertLinkModal.on('hide', function() { 319 | self.editor.currentView.element.focus(); 320 | }); 321 | 322 | toolbar.find('a[data-wysihtml5-command=createLink]').click(function() { 323 | var activeButton = $(this).hasClass("wysihtml5-command-active"); 324 | 325 | if (!activeButton) { 326 | self.editor.currentView.element.focus(false); 327 | caretBookmark = self.editor.composer.selection.getBookmark(); 328 | insertLinkModal.appendTo('body').modal('show'); 329 | insertLinkModal.on('click.dismiss.modal', '[data-dismiss="modal"]', function(e) { 330 | e.stopPropagation(); 331 | }); 332 | return false; 333 | } 334 | else { 335 | return true; 336 | } 337 | }); 338 | } 339 | }; 340 | 341 | // these define our public api 342 | var methods = { 343 | resetDefaults: function() { 344 | $.fn.wysihtml5.defaultOptions = $.extend(true, {}, $.fn.wysihtml5.defaultOptionsCache); 345 | }, 346 | bypassDefaults: function(options) { 347 | return this.each(function () { 348 | var $this = $(this); 349 | $this.data('wysihtml5', new Wysihtml5($this, options)); 350 | }); 351 | }, 352 | shallowExtend: function (options) { 353 | var settings = $.extend({}, $.fn.wysihtml5.defaultOptions, options || {}); 354 | var that = this; 355 | return methods.bypassDefaults.apply(that, [settings]); 356 | }, 357 | deepExtend: function(options) { 358 | var settings = $.extend(true, {}, $.fn.wysihtml5.defaultOptions, options || {}); 359 | var that = this; 360 | return methods.bypassDefaults.apply(that, [settings]); 361 | }, 362 | init: function(options) { 363 | var that = this; 364 | return methods.shallowExtend.apply(that, [options]); 365 | } 366 | }; 367 | 368 | $.fn.wysihtml5 = function ( method ) { 369 | if ( methods[method] ) { 370 | return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); 371 | } else if ( typeof method === 'object' || ! method ) { 372 | return methods.init.apply( this, arguments ); 373 | } else { 374 | $.error( 'Method ' + method + ' does not exist on jQuery.wysihtml5' ); 375 | } 376 | }; 377 | 378 | $.fn.wysihtml5.Constructor = Wysihtml5; 379 | 380 | var defaultOptions = $.fn.wysihtml5.defaultOptions = { 381 | "font-styles": true, 382 | "color": false, 383 | "emphasis": true, 384 | "lists": true, 385 | "html": false, 386 | "link": true, 387 | "image": true, 388 | events: {}, 389 | parserRules: { 390 | classes: { 391 | // (path_to_project/lib/css/wysiwyg-color.css) 392 | "wysiwyg-color-silver" : 1, 393 | "wysiwyg-color-gray" : 1, 394 | "wysiwyg-color-white" : 1, 395 | "wysiwyg-color-maroon" : 1, 396 | "wysiwyg-color-red" : 1, 397 | "wysiwyg-color-purple" : 1, 398 | "wysiwyg-color-fuchsia" : 1, 399 | "wysiwyg-color-green" : 1, 400 | "wysiwyg-color-lime" : 1, 401 | "wysiwyg-color-olive" : 1, 402 | "wysiwyg-color-yellow" : 1, 403 | "wysiwyg-color-navy" : 1, 404 | "wysiwyg-color-blue" : 1, 405 | "wysiwyg-color-teal" : 1, 406 | "wysiwyg-color-aqua" : 1, 407 | "wysiwyg-color-orange" : 1 408 | }, 409 | tags: { 410 | "b": {}, 411 | "i": {}, 412 | "br": {}, 413 | "ol": {}, 414 | "ul": {}, 415 | "li": {}, 416 | "h1": {}, 417 | "h2": {}, 418 | "h3": {}, 419 | "blockquote": {}, 420 | "u": 1, 421 | "img": { 422 | "check_attributes": { 423 | "width": "numbers", 424 | "alt": "alt", 425 | "src": "url", 426 | "height": "numbers" 427 | } 428 | }, 429 | "a": { 430 | set_attributes: { 431 | target: "_blank", 432 | rel: "nofollow" 433 | }, 434 | check_attributes: { 435 | href: "url" // important to avoid XSS 436 | } 437 | }, 438 | "span": 1, 439 | "div": 1, 440 | // to allow save and edit files with code tag hacks 441 | "code": 1, 442 | "pre": 1 443 | } 444 | }, 445 | stylesheets: ["./lib/css/wysiwyg-color.css"], // (path_to_project/lib/css/wysiwyg-color.css) 446 | locale: "en" 447 | }; 448 | 449 | if (typeof $.fn.wysihtml5.defaultOptionsCache === 'undefined') { 450 | $.fn.wysihtml5.defaultOptionsCache = $.extend(true, {}, $.fn.wysihtml5.defaultOptions); 451 | } 452 | 453 | var locale = $.fn.wysihtml5.locale = { 454 | en: { 455 | font_styles: { 456 | normal: "Normal text", 457 | h1: "Heading 1", 458 | h2: "Heading 2", 459 | h3: "Heading 3" 460 | }, 461 | emphasis: { 462 | bold: "Bold", 463 | italic: "Italic", 464 | underline: "Underline" 465 | }, 466 | lists: { 467 | unordered: "Unordered list", 468 | ordered: "Ordered list", 469 | outdent: "Outdent", 470 | indent: "Indent" 471 | }, 472 | link: { 473 | insert: "Insert link", 474 | cancel: "Cancel" 475 | }, 476 | image: { 477 | insert: "Insert image", 478 | cancel: "Cancel" 479 | }, 480 | html: { 481 | edit: "Edit HTML" 482 | }, 483 | colours: { 484 | black: "Black", 485 | silver: "Silver", 486 | gray: "Grey", 487 | maroon: "Maroon", 488 | red: "Red", 489 | purple: "Purple", 490 | green: "Green", 491 | olive: "Olive", 492 | navy: "Navy", 493 | blue: "Blue", 494 | orange: "Orange" 495 | } 496 | } 497 | }; 498 | 499 | }(window.jQuery, window.wysihtml5); 500 | -------------------------------------------------------------------------------- /app/assets/javascripts/inputs-ext/typeaheadjs/lib/typeahead.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * typeahead.js 0.9.3 3 | * https://github.com/twitter/typeahead 4 | * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT 5 | */ 6 | 7 | (function($) { 8 | var VERSION = "0.9.3"; 9 | var utils = { 10 | isMsie: function() { 11 | var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); 12 | return match ? parseInt(match[2], 10) : false; 13 | }, 14 | isBlankString: function(str) { 15 | return !str || /^\s*$/.test(str); 16 | }, 17 | escapeRegExChars: function(str) { 18 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 19 | }, 20 | isString: function(obj) { 21 | return typeof obj === "string"; 22 | }, 23 | isNumber: function(obj) { 24 | return typeof obj === "number"; 25 | }, 26 | isArray: $.isArray, 27 | isFunction: $.isFunction, 28 | isObject: $.isPlainObject, 29 | isUndefined: function(obj) { 30 | return typeof obj === "undefined"; 31 | }, 32 | bind: $.proxy, 33 | bindAll: function(obj) { 34 | var val; 35 | for (var key in obj) { 36 | $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); 37 | } 38 | }, 39 | indexOf: function(haystack, needle) { 40 | for (var i = 0; i < haystack.length; i++) { 41 | if (haystack[i] === needle) { 42 | return i; 43 | } 44 | } 45 | return -1; 46 | }, 47 | each: $.each, 48 | map: $.map, 49 | filter: $.grep, 50 | every: function(obj, test) { 51 | var result = true; 52 | if (!obj) { 53 | return result; 54 | } 55 | $.each(obj, function(key, val) { 56 | if (!(result = test.call(null, val, key, obj))) { 57 | return false; 58 | } 59 | }); 60 | return !!result; 61 | }, 62 | some: function(obj, test) { 63 | var result = false; 64 | if (!obj) { 65 | return result; 66 | } 67 | $.each(obj, function(key, val) { 68 | if (result = test.call(null, val, key, obj)) { 69 | return false; 70 | } 71 | }); 72 | return !!result; 73 | }, 74 | mixin: $.extend, 75 | getUniqueId: function() { 76 | var counter = 0; 77 | return function() { 78 | return counter++; 79 | }; 80 | }(), 81 | defer: function(fn) { 82 | setTimeout(fn, 0); 83 | }, 84 | debounce: function(func, wait, immediate) { 85 | var timeout, result; 86 | return function() { 87 | var context = this, args = arguments, later, callNow; 88 | later = function() { 89 | timeout = null; 90 | if (!immediate) { 91 | result = func.apply(context, args); 92 | } 93 | }; 94 | callNow = immediate && !timeout; 95 | clearTimeout(timeout); 96 | timeout = setTimeout(later, wait); 97 | if (callNow) { 98 | result = func.apply(context, args); 99 | } 100 | return result; 101 | }; 102 | }, 103 | throttle: function(func, wait) { 104 | var context, args, timeout, result, previous, later; 105 | previous = 0; 106 | later = function() { 107 | previous = new Date(); 108 | timeout = null; 109 | result = func.apply(context, args); 110 | }; 111 | return function() { 112 | var now = new Date(), remaining = wait - (now - previous); 113 | context = this; 114 | args = arguments; 115 | if (remaining <= 0) { 116 | clearTimeout(timeout); 117 | timeout = null; 118 | previous = now; 119 | result = func.apply(context, args); 120 | } else if (!timeout) { 121 | timeout = setTimeout(later, remaining); 122 | } 123 | return result; 124 | }; 125 | }, 126 | tokenizeQuery: function(str) { 127 | return $.trim(str).toLowerCase().split(/[\s]+/); 128 | }, 129 | tokenizeText: function(str) { 130 | return $.trim(str).toLowerCase().split(/[\s\-_]+/); 131 | }, 132 | getProtocol: function() { 133 | return location.protocol; 134 | }, 135 | noop: function() {} 136 | }; 137 | var EventTarget = function() { 138 | var eventSplitter = /\s+/; 139 | return { 140 | on: function(events, callback) { 141 | var event; 142 | if (!callback) { 143 | return this; 144 | } 145 | this._callbacks = this._callbacks || {}; 146 | events = events.split(eventSplitter); 147 | while (event = events.shift()) { 148 | this._callbacks[event] = this._callbacks[event] || []; 149 | this._callbacks[event].push(callback); 150 | } 151 | return this; 152 | }, 153 | trigger: function(events, data) { 154 | var event, callbacks; 155 | if (!this._callbacks) { 156 | return this; 157 | } 158 | events = events.split(eventSplitter); 159 | while (event = events.shift()) { 160 | if (callbacks = this._callbacks[event]) { 161 | for (var i = 0; i < callbacks.length; i += 1) { 162 | callbacks[i].call(this, { 163 | type: event, 164 | data: data 165 | }); 166 | } 167 | } 168 | } 169 | return this; 170 | } 171 | }; 172 | }(); 173 | var EventBus = function() { 174 | var namespace = "typeahead:"; 175 | function EventBus(o) { 176 | if (!o || !o.el) { 177 | $.error("EventBus initialized without el"); 178 | } 179 | this.$el = $(o.el); 180 | } 181 | utils.mixin(EventBus.prototype, { 182 | trigger: function(type) { 183 | var args = [].slice.call(arguments, 1); 184 | this.$el.trigger(namespace + type, args); 185 | } 186 | }); 187 | return EventBus; 188 | }(); 189 | var PersistentStorage = function() { 190 | var ls, methods; 191 | try { 192 | ls = window.localStorage; 193 | ls.setItem("~~~", "!"); 194 | ls.removeItem("~~~"); 195 | } catch (err) { 196 | ls = null; 197 | } 198 | function PersistentStorage(namespace) { 199 | this.prefix = [ "__", namespace, "__" ].join(""); 200 | this.ttlKey = "__ttl__"; 201 | this.keyMatcher = new RegExp("^" + this.prefix); 202 | } 203 | if (ls && window.JSON) { 204 | methods = { 205 | _prefix: function(key) { 206 | return this.prefix + key; 207 | }, 208 | _ttlKey: function(key) { 209 | return this._prefix(key) + this.ttlKey; 210 | }, 211 | get: function(key) { 212 | if (this.isExpired(key)) { 213 | this.remove(key); 214 | } 215 | return decode(ls.getItem(this._prefix(key))); 216 | }, 217 | set: function(key, val, ttl) { 218 | if (utils.isNumber(ttl)) { 219 | ls.setItem(this._ttlKey(key), encode(now() + ttl)); 220 | } else { 221 | ls.removeItem(this._ttlKey(key)); 222 | } 223 | return ls.setItem(this._prefix(key), encode(val)); 224 | }, 225 | remove: function(key) { 226 | ls.removeItem(this._ttlKey(key)); 227 | ls.removeItem(this._prefix(key)); 228 | return this; 229 | }, 230 | clear: function() { 231 | var i, key, keys = [], len = ls.length; 232 | for (i = 0; i < len; i++) { 233 | if ((key = ls.key(i)).match(this.keyMatcher)) { 234 | keys.push(key.replace(this.keyMatcher, "")); 235 | } 236 | } 237 | for (i = keys.length; i--; ) { 238 | this.remove(keys[i]); 239 | } 240 | return this; 241 | }, 242 | isExpired: function(key) { 243 | var ttl = decode(ls.getItem(this._ttlKey(key))); 244 | return utils.isNumber(ttl) && now() > ttl ? true : false; 245 | } 246 | }; 247 | } else { 248 | methods = { 249 | get: utils.noop, 250 | set: utils.noop, 251 | remove: utils.noop, 252 | clear: utils.noop, 253 | isExpired: utils.noop 254 | }; 255 | } 256 | utils.mixin(PersistentStorage.prototype, methods); 257 | return PersistentStorage; 258 | function now() { 259 | return new Date().getTime(); 260 | } 261 | function encode(val) { 262 | return JSON.stringify(utils.isUndefined(val) ? null : val); 263 | } 264 | function decode(val) { 265 | return JSON.parse(val); 266 | } 267 | }(); 268 | var RequestCache = function() { 269 | function RequestCache(o) { 270 | utils.bindAll(this); 271 | o = o || {}; 272 | this.sizeLimit = o.sizeLimit || 10; 273 | this.cache = {}; 274 | this.cachedKeysByAge = []; 275 | } 276 | utils.mixin(RequestCache.prototype, { 277 | get: function(url) { 278 | return this.cache[url]; 279 | }, 280 | set: function(url, resp) { 281 | var requestToEvict; 282 | if (this.cachedKeysByAge.length === this.sizeLimit) { 283 | requestToEvict = this.cachedKeysByAge.shift(); 284 | delete this.cache[requestToEvict]; 285 | } 286 | this.cache[url] = resp; 287 | this.cachedKeysByAge.push(url); 288 | } 289 | }); 290 | return RequestCache; 291 | }(); 292 | var Transport = function() { 293 | var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; 294 | function Transport(o) { 295 | utils.bindAll(this); 296 | o = utils.isString(o) ? { 297 | url: o 298 | } : o; 299 | requestCache = requestCache || new RequestCache(); 300 | maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; 301 | this.url = o.url; 302 | this.wildcard = o.wildcard || "%QUERY"; 303 | this.filter = o.filter; 304 | this.replace = o.replace; 305 | this.ajaxSettings = { 306 | type: "get", 307 | cache: o.cache, 308 | timeout: o.timeout, 309 | dataType: o.dataType || "json", 310 | beforeSend: o.beforeSend 311 | }; 312 | this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); 313 | } 314 | utils.mixin(Transport.prototype, { 315 | _get: function(url, cb) { 316 | var that = this; 317 | if (belowPendingRequestsThreshold()) { 318 | this._sendRequest(url).done(done); 319 | } else { 320 | this.onDeckRequestArgs = [].slice.call(arguments, 0); 321 | } 322 | function done(resp) { 323 | var data = that.filter ? that.filter(resp) : resp; 324 | cb && cb(data); 325 | requestCache.set(url, resp); 326 | } 327 | }, 328 | _sendRequest: function(url) { 329 | var that = this, jqXhr = pendingRequests[url]; 330 | if (!jqXhr) { 331 | incrementPendingRequests(); 332 | jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); 333 | } 334 | return jqXhr; 335 | function always() { 336 | decrementPendingRequests(); 337 | pendingRequests[url] = null; 338 | if (that.onDeckRequestArgs) { 339 | that._get.apply(that, that.onDeckRequestArgs); 340 | that.onDeckRequestArgs = null; 341 | } 342 | } 343 | }, 344 | get: function(query, cb) { 345 | var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; 346 | cb = cb || utils.noop; 347 | url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); 348 | if (resp = requestCache.get(url)) { 349 | utils.defer(function() { 350 | cb(that.filter ? that.filter(resp) : resp); 351 | }); 352 | } else { 353 | this._get(url, cb); 354 | } 355 | return !!resp; 356 | } 357 | }); 358 | return Transport; 359 | function incrementPendingRequests() { 360 | pendingRequestsCount++; 361 | } 362 | function decrementPendingRequests() { 363 | pendingRequestsCount--; 364 | } 365 | function belowPendingRequestsThreshold() { 366 | return pendingRequestsCount < maxPendingRequests; 367 | } 368 | }(); 369 | var Dataset = function() { 370 | var keys = { 371 | thumbprint: "thumbprint", 372 | protocol: "protocol", 373 | itemHash: "itemHash", 374 | adjacencyList: "adjacencyList" 375 | }; 376 | function Dataset(o) { 377 | utils.bindAll(this); 378 | if (utils.isString(o.template) && !o.engine) { 379 | $.error("no template engine specified"); 380 | } 381 | if (!o.local && !o.prefetch && !o.remote) { 382 | $.error("one of local, prefetch, or remote is required"); 383 | } 384 | this.name = o.name || utils.getUniqueId(); 385 | this.limit = o.limit || 5; 386 | this.minLength = o.minLength || 1; 387 | this.header = o.header; 388 | this.footer = o.footer; 389 | this.valueKey = o.valueKey || "value"; 390 | this.template = compileTemplate(o.template, o.engine, this.valueKey); 391 | this.local = o.local; 392 | this.prefetch = o.prefetch; 393 | this.remote = o.remote; 394 | this.itemHash = {}; 395 | this.adjacencyList = {}; 396 | this.storage = o.name ? new PersistentStorage(o.name) : null; 397 | } 398 | utils.mixin(Dataset.prototype, { 399 | _processLocalData: function(data) { 400 | this._mergeProcessedData(this._processData(data)); 401 | }, 402 | _loadPrefetchData: function(o) { 403 | var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; 404 | if (this.storage) { 405 | storedThumbprint = this.storage.get(keys.thumbprint); 406 | storedProtocol = this.storage.get(keys.protocol); 407 | storedItemHash = this.storage.get(keys.itemHash); 408 | storedAdjacencyList = this.storage.get(keys.adjacencyList); 409 | } 410 | isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); 411 | o = utils.isString(o) ? { 412 | url: o 413 | } : o; 414 | o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; 415 | if (storedItemHash && storedAdjacencyList && !isExpired) { 416 | this._mergeProcessedData({ 417 | itemHash: storedItemHash, 418 | adjacencyList: storedAdjacencyList 419 | }); 420 | deferred = $.Deferred().resolve(); 421 | } else { 422 | deferred = $.getJSON(o.url).done(processPrefetchData); 423 | } 424 | return deferred; 425 | function processPrefetchData(data) { 426 | var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; 427 | if (that.storage) { 428 | that.storage.set(keys.itemHash, itemHash, o.ttl); 429 | that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); 430 | that.storage.set(keys.thumbprint, thumbprint, o.ttl); 431 | that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); 432 | } 433 | that._mergeProcessedData(processedData); 434 | } 435 | }, 436 | _transformDatum: function(datum) { 437 | var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { 438 | value: value, 439 | tokens: tokens 440 | }; 441 | if (utils.isString(datum)) { 442 | item.datum = {}; 443 | item.datum[this.valueKey] = datum; 444 | } else { 445 | item.datum = datum; 446 | } 447 | item.tokens = utils.filter(item.tokens, function(token) { 448 | return !utils.isBlankString(token); 449 | }); 450 | item.tokens = utils.map(item.tokens, function(token) { 451 | return token.toLowerCase(); 452 | }); 453 | return item; 454 | }, 455 | _processData: function(data) { 456 | var that = this, itemHash = {}, adjacencyList = {}; 457 | utils.each(data, function(i, datum) { 458 | var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); 459 | itemHash[id] = item; 460 | utils.each(item.tokens, function(i, token) { 461 | var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); 462 | !~utils.indexOf(adjacency, id) && adjacency.push(id); 463 | }); 464 | }); 465 | return { 466 | itemHash: itemHash, 467 | adjacencyList: adjacencyList 468 | }; 469 | }, 470 | _mergeProcessedData: function(processedData) { 471 | var that = this; 472 | utils.mixin(this.itemHash, processedData.itemHash); 473 | utils.each(processedData.adjacencyList, function(character, adjacency) { 474 | var masterAdjacency = that.adjacencyList[character]; 475 | that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; 476 | }); 477 | }, 478 | _getLocalSuggestions: function(terms) { 479 | var that = this, firstChars = [], lists = [], shortestList, suggestions = []; 480 | utils.each(terms, function(i, term) { 481 | var firstChar = term.charAt(0); 482 | !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); 483 | }); 484 | utils.each(firstChars, function(i, firstChar) { 485 | var list = that.adjacencyList[firstChar]; 486 | if (!list) { 487 | return false; 488 | } 489 | lists.push(list); 490 | if (!shortestList || list.length < shortestList.length) { 491 | shortestList = list; 492 | } 493 | }); 494 | if (lists.length < firstChars.length) { 495 | return []; 496 | } 497 | utils.each(shortestList, function(i, id) { 498 | var item = that.itemHash[id], isCandidate, isMatch; 499 | isCandidate = utils.every(lists, function(list) { 500 | return ~utils.indexOf(list, id); 501 | }); 502 | isMatch = isCandidate && utils.every(terms, function(term) { 503 | return utils.some(item.tokens, function(token) { 504 | return token.indexOf(term) === 0; 505 | }); 506 | }); 507 | isMatch && suggestions.push(item); 508 | }); 509 | return suggestions; 510 | }, 511 | initialize: function() { 512 | var deferred; 513 | this.local && this._processLocalData(this.local); 514 | this.transport = this.remote ? new Transport(this.remote) : null; 515 | deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); 516 | this.local = this.prefetch = this.remote = null; 517 | this.initialize = function() { 518 | return deferred; 519 | }; 520 | return deferred; 521 | }, 522 | getSuggestions: function(query, cb) { 523 | var that = this, terms, suggestions, cacheHit = false; 524 | if (query.length < this.minLength) { 525 | return; 526 | } 527 | terms = utils.tokenizeQuery(query); 528 | suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); 529 | if (suggestions.length < this.limit && this.transport) { 530 | cacheHit = this.transport.get(query, processRemoteData); 531 | } 532 | !cacheHit && cb && cb(suggestions); 533 | function processRemoteData(data) { 534 | suggestions = suggestions.slice(0); 535 | utils.each(data, function(i, datum) { 536 | var item = that._transformDatum(datum), isDuplicate; 537 | isDuplicate = utils.some(suggestions, function(suggestion) { 538 | return item.value === suggestion.value; 539 | }); 540 | !isDuplicate && suggestions.push(item); 541 | return suggestions.length < that.limit; 542 | }); 543 | cb && cb(suggestions); 544 | } 545 | } 546 | }); 547 | return Dataset; 548 | function compileTemplate(template, engine, valueKey) { 549 | var renderFn, compiledTemplate; 550 | if (utils.isFunction(template)) { 551 | renderFn = template; 552 | } else if (utils.isString(template)) { 553 | compiledTemplate = engine.compile(template); 554 | renderFn = utils.bind(compiledTemplate.render, compiledTemplate); 555 | } else { 556 | renderFn = function(context) { 557 | return "

        " + context[valueKey] + "

        "; 558 | }; 559 | } 560 | return renderFn; 561 | } 562 | }(); 563 | var InputView = function() { 564 | function InputView(o) { 565 | var that = this; 566 | utils.bindAll(this); 567 | this.specialKeyCodeMap = { 568 | 9: "tab", 569 | 27: "esc", 570 | 37: "left", 571 | 39: "right", 572 | 13: "enter", 573 | 38: "up", 574 | 40: "down" 575 | }; 576 | this.$hint = $(o.hint); 577 | this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); 578 | if (!utils.isMsie()) { 579 | this.$input.on("input.tt", this._compareQueryToInputValue); 580 | } else { 581 | this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { 582 | if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { 583 | return; 584 | } 585 | utils.defer(that._compareQueryToInputValue); 586 | }); 587 | } 588 | this.query = this.$input.val(); 589 | this.$overflowHelper = buildOverflowHelper(this.$input); 590 | } 591 | utils.mixin(InputView.prototype, EventTarget, { 592 | _handleFocus: function() { 593 | this.trigger("focused"); 594 | }, 595 | _handleBlur: function() { 596 | this.trigger("blured"); 597 | }, 598 | _handleSpecialKeyEvent: function($e) { 599 | var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; 600 | keyName && this.trigger(keyName + "Keyed", $e); 601 | }, 602 | _compareQueryToInputValue: function() { 603 | var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; 604 | if (isSameQueryExceptWhitespace) { 605 | this.trigger("whitespaceChanged", { 606 | value: this.query 607 | }); 608 | } else if (!isSameQuery) { 609 | this.trigger("queryChanged", { 610 | value: this.query = inputValue 611 | }); 612 | } 613 | }, 614 | destroy: function() { 615 | this.$hint.off(".tt"); 616 | this.$input.off(".tt"); 617 | this.$hint = this.$input = this.$overflowHelper = null; 618 | }, 619 | focus: function() { 620 | this.$input.focus(); 621 | }, 622 | blur: function() { 623 | this.$input.blur(); 624 | }, 625 | getQuery: function() { 626 | return this.query; 627 | }, 628 | setQuery: function(query) { 629 | this.query = query; 630 | }, 631 | getInputValue: function() { 632 | return this.$input.val(); 633 | }, 634 | setInputValue: function(value, silent) { 635 | this.$input.val(value); 636 | !silent && this._compareQueryToInputValue(); 637 | }, 638 | getHintValue: function() { 639 | return this.$hint.val(); 640 | }, 641 | setHintValue: function(value) { 642 | this.$hint.val(value); 643 | }, 644 | getLanguageDirection: function() { 645 | return (this.$input.css("direction") || "ltr").toLowerCase(); 646 | }, 647 | isOverflow: function() { 648 | this.$overflowHelper.text(this.getInputValue()); 649 | return this.$overflowHelper.width() > this.$input.width(); 650 | }, 651 | isCursorAtEnd: function() { 652 | var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; 653 | if (utils.isNumber(selectionStart)) { 654 | return selectionStart === valueLength; 655 | } else if (document.selection) { 656 | range = document.selection.createRange(); 657 | range.moveStart("character", -valueLength); 658 | return valueLength === range.text.length; 659 | } 660 | return true; 661 | } 662 | }); 663 | return InputView; 664 | function buildOverflowHelper($input) { 665 | return $("").css({ 666 | position: "absolute", 667 | left: "-9999px", 668 | visibility: "hidden", 669 | whiteSpace: "nowrap", 670 | fontFamily: $input.css("font-family"), 671 | fontSize: $input.css("font-size"), 672 | fontStyle: $input.css("font-style"), 673 | fontVariant: $input.css("font-variant"), 674 | fontWeight: $input.css("font-weight"), 675 | wordSpacing: $input.css("word-spacing"), 676 | letterSpacing: $input.css("letter-spacing"), 677 | textIndent: $input.css("text-indent"), 678 | textRendering: $input.css("text-rendering"), 679 | textTransform: $input.css("text-transform") 680 | }).insertAfter($input); 681 | } 682 | function compareQueries(a, b) { 683 | a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); 684 | b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); 685 | return a === b; 686 | } 687 | }(); 688 | var DropdownView = function() { 689 | var html = { 690 | suggestionsList: '' 691 | }, css = { 692 | suggestionsList: { 693 | display: "block" 694 | }, 695 | suggestion: { 696 | whiteSpace: "nowrap", 697 | cursor: "pointer" 698 | }, 699 | suggestionChild: { 700 | whiteSpace: "normal" 701 | } 702 | }; 703 | function DropdownView(o) { 704 | utils.bindAll(this); 705 | this.isOpen = false; 706 | this.isEmpty = true; 707 | this.isMouseOverDropdown = false; 708 | 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); 709 | } 710 | utils.mixin(DropdownView.prototype, EventTarget, { 711 | _handleMouseenter: function() { 712 | this.isMouseOverDropdown = true; 713 | }, 714 | _handleMouseleave: function() { 715 | this.isMouseOverDropdown = false; 716 | }, 717 | _handleMouseover: function($e) { 718 | var $suggestion = $($e.currentTarget); 719 | this._getSuggestions().removeClass("tt-is-under-cursor"); 720 | $suggestion.addClass("tt-is-under-cursor"); 721 | }, 722 | _handleSelection: function($e) { 723 | var $suggestion = $($e.currentTarget); 724 | this.trigger("suggestionSelected", extractSuggestion($suggestion)); 725 | }, 726 | _show: function() { 727 | this.$menu.css("display", "block"); 728 | }, 729 | _hide: function() { 730 | this.$menu.hide(); 731 | }, 732 | _moveCursor: function(increment) { 733 | var $suggestions, $cur, nextIndex, $underCursor; 734 | if (!this.isVisible()) { 735 | return; 736 | } 737 | $suggestions = this._getSuggestions(); 738 | $cur = $suggestions.filter(".tt-is-under-cursor"); 739 | $cur.removeClass("tt-is-under-cursor"); 740 | nextIndex = $suggestions.index($cur) + increment; 741 | nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; 742 | if (nextIndex === -1) { 743 | this.trigger("cursorRemoved"); 744 | return; 745 | } else if (nextIndex < -1) { 746 | nextIndex = $suggestions.length - 1; 747 | } 748 | $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); 749 | this._ensureVisibility($underCursor); 750 | this.trigger("cursorMoved", extractSuggestion($underCursor)); 751 | }, 752 | _getSuggestions: function() { 753 | return this.$menu.find(".tt-suggestions > .tt-suggestion"); 754 | }, 755 | _ensureVisibility: function($el) { 756 | 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); 757 | if (elTop < 0) { 758 | this.$menu.scrollTop(menuScrollTop + elTop); 759 | } else if (menuHeight < elBottom) { 760 | this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); 761 | } 762 | }, 763 | destroy: function() { 764 | this.$menu.off(".tt"); 765 | this.$menu = null; 766 | }, 767 | isVisible: function() { 768 | return this.isOpen && !this.isEmpty; 769 | }, 770 | closeUnlessMouseIsOverDropdown: function() { 771 | if (!this.isMouseOverDropdown) { 772 | this.close(); 773 | } 774 | }, 775 | close: function() { 776 | if (this.isOpen) { 777 | this.isOpen = false; 778 | this.isMouseOverDropdown = false; 779 | this._hide(); 780 | this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); 781 | this.trigger("closed"); 782 | } 783 | }, 784 | open: function() { 785 | if (!this.isOpen) { 786 | this.isOpen = true; 787 | !this.isEmpty && this._show(); 788 | this.trigger("opened"); 789 | } 790 | }, 791 | setLanguageDirection: function(dir) { 792 | var ltrCss = { 793 | left: "0", 794 | right: "auto" 795 | }, rtlCss = { 796 | left: "auto", 797 | right: " 0" 798 | }; 799 | dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); 800 | }, 801 | moveCursorUp: function() { 802 | this._moveCursor(-1); 803 | }, 804 | moveCursorDown: function() { 805 | this._moveCursor(+1); 806 | }, 807 | getSuggestionUnderCursor: function() { 808 | var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); 809 | return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; 810 | }, 811 | getFirstSuggestion: function() { 812 | var $suggestion = this._getSuggestions().first(); 813 | return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; 814 | }, 815 | renderSuggestions: function(dataset, suggestions) { 816 | var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '
        %body
        ', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el; 817 | if ($dataset.length === 0) { 818 | $suggestionsList = $(html.suggestionsList).css(css.suggestionsList); 819 | $dataset = $("
        ").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu); 820 | } 821 | if (suggestions.length > 0) { 822 | this.isEmpty = false; 823 | this.isOpen && this._show(); 824 | elBuilder = document.createElement("div"); 825 | fragment = document.createDocumentFragment(); 826 | utils.each(suggestions, function(i, suggestion) { 827 | suggestion.dataset = dataset.name; 828 | compiledHtml = dataset.template(suggestion.datum); 829 | elBuilder.innerHTML = wrapper.replace("%body", compiledHtml); 830 | $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion); 831 | $el.children().each(function() { 832 | $(this).css(css.suggestionChild); 833 | }); 834 | fragment.appendChild($el[0]); 835 | }); 836 | $dataset.show().find(".tt-suggestions").html(fragment); 837 | } else { 838 | this.clearSuggestions(dataset.name); 839 | } 840 | this.trigger("suggestionsRendered"); 841 | }, 842 | clearSuggestions: function(datasetName) { 843 | var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions"); 844 | $datasets.hide(); 845 | $suggestions.empty(); 846 | if (this._getSuggestions().length === 0) { 847 | this.isEmpty = true; 848 | this._hide(); 849 | } 850 | } 851 | }); 852 | return DropdownView; 853 | function extractSuggestion($el) { 854 | return $el.data("suggestion"); 855 | } 856 | }(); 857 | var TypeaheadView = function() { 858 | var html = { 859 | wrapper: '', 860 | hint: '', 861 | dropdown: '' 862 | }, css = { 863 | wrapper: { 864 | position: "relative", 865 | display: "inline-block" 866 | }, 867 | hint: { 868 | position: "absolute", 869 | top: "0", 870 | left: "0", 871 | borderColor: "transparent", 872 | boxShadow: "none" 873 | }, 874 | query: { 875 | position: "relative", 876 | verticalAlign: "top", 877 | backgroundColor: "transparent" 878 | }, 879 | dropdown: { 880 | position: "absolute", 881 | top: "100%", 882 | left: "0", 883 | zIndex: "100", 884 | display: "none" 885 | } 886 | }; 887 | if (utils.isMsie()) { 888 | utils.mixin(css.query, { 889 | backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" 890 | }); 891 | } 892 | if (utils.isMsie() && utils.isMsie() <= 7) { 893 | utils.mixin(css.wrapper, { 894 | display: "inline", 895 | zoom: "1" 896 | }); 897 | utils.mixin(css.query, { 898 | marginTop: "-1px" 899 | }); 900 | } 901 | function TypeaheadView(o) { 902 | var $menu, $input, $hint; 903 | utils.bindAll(this); 904 | this.$node = buildDomStructure(o.input); 905 | this.datasets = o.datasets; 906 | this.dir = null; 907 | this.eventBus = o.eventBus; 908 | $menu = this.$node.find(".tt-dropdown-menu"); 909 | $input = this.$node.find(".tt-query"); 910 | $hint = this.$node.find(".tt-hint"); 911 | this.dropdownView = new DropdownView({ 912 | menu: $menu 913 | }).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); 914 | this.inputView = new InputView({ 915 | input: $input, 916 | hint: $hint 917 | }).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); 918 | } 919 | utils.mixin(TypeaheadView.prototype, EventTarget, { 920 | _managePreventDefault: function(e) { 921 | var $e = e.data, hint, inputValue, preventDefault = false; 922 | switch (e.type) { 923 | case "tabKeyed": 924 | hint = this.inputView.getHintValue(); 925 | inputValue = this.inputView.getInputValue(); 926 | preventDefault = hint && hint !== inputValue; 927 | break; 928 | 929 | case "upKeyed": 930 | case "downKeyed": 931 | preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey; 932 | break; 933 | } 934 | preventDefault && $e.preventDefault(); 935 | }, 936 | _setLanguageDirection: function() { 937 | var dir = this.inputView.getLanguageDirection(); 938 | if (dir !== this.dir) { 939 | this.dir = dir; 940 | this.$node.css("direction", dir); 941 | this.dropdownView.setLanguageDirection(dir); 942 | } 943 | }, 944 | _updateHint: function() { 945 | var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match; 946 | if (hint && dropdownIsVisible && !inputHasOverflow) { 947 | inputValue = this.inputView.getInputValue(); 948 | query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, ""); 949 | escapedQuery = utils.escapeRegExChars(query); 950 | beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); 951 | match = beginsWithQuery.exec(hint); 952 | this.inputView.setHintValue(inputValue + (match ? match[1] : "")); 953 | } 954 | }, 955 | _clearHint: function() { 956 | this.inputView.setHintValue(""); 957 | }, 958 | _clearSuggestions: function() { 959 | this.dropdownView.clearSuggestions(); 960 | }, 961 | _setInputValueToQuery: function() { 962 | this.inputView.setInputValue(this.inputView.getQuery()); 963 | }, 964 | _setInputValueToSuggestionUnderCursor: function(e) { 965 | var suggestion = e.data; 966 | this.inputView.setInputValue(suggestion.value, true); 967 | }, 968 | _openDropdown: function() { 969 | this.dropdownView.open(); 970 | }, 971 | _closeDropdown: function(e) { 972 | this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"](); 973 | }, 974 | _moveDropdownCursor: function(e) { 975 | var $e = e.data; 976 | if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) { 977 | this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"](); 978 | } 979 | }, 980 | _handleSelection: function(e) { 981 | var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor(); 982 | if (suggestion) { 983 | this.inputView.setInputValue(suggestion.value); 984 | byClick ? this.inputView.focus() : e.data.preventDefault(); 985 | byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close(); 986 | this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset); 987 | } 988 | }, 989 | _getSuggestions: function() { 990 | var that = this, query = this.inputView.getQuery(); 991 | if (utils.isBlankString(query)) { 992 | return; 993 | } 994 | utils.each(this.datasets, function(i, dataset) { 995 | dataset.getSuggestions(query, function(suggestions) { 996 | if (query === that.inputView.getQuery()) { 997 | that.dropdownView.renderSuggestions(dataset, suggestions); 998 | } 999 | }); 1000 | }); 1001 | }, 1002 | _autocomplete: function(e) { 1003 | var isCursorAtEnd, ignoreEvent, query, hint, suggestion; 1004 | if (e.type === "rightKeyed" || e.type === "leftKeyed") { 1005 | isCursorAtEnd = this.inputView.isCursorAtEnd(); 1006 | ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed"; 1007 | if (!isCursorAtEnd || ignoreEvent) { 1008 | return; 1009 | } 1010 | } 1011 | query = this.inputView.getQuery(); 1012 | hint = this.inputView.getHintValue(); 1013 | if (hint !== "" && query !== hint) { 1014 | suggestion = this.dropdownView.getFirstSuggestion(); 1015 | this.inputView.setInputValue(suggestion.value); 1016 | this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset); 1017 | } 1018 | }, 1019 | _propagateEvent: function(e) { 1020 | this.eventBus.trigger(e.type); 1021 | }, 1022 | destroy: function() { 1023 | this.inputView.destroy(); 1024 | this.dropdownView.destroy(); 1025 | destroyDomStructure(this.$node); 1026 | this.$node = null; 1027 | }, 1028 | setQuery: function(query) { 1029 | this.inputView.setQuery(query); 1030 | this.inputView.setInputValue(query); 1031 | this._clearHint(); 1032 | this._clearSuggestions(); 1033 | this._getSuggestions(); 1034 | } 1035 | }); 1036 | return TypeaheadView; 1037 | function buildDomStructure(input) { 1038 | var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint); 1039 | $wrapper = $wrapper.css(css.wrapper); 1040 | $dropdown = $dropdown.css(css.dropdown); 1041 | $hint.css(css.hint).css({ 1042 | backgroundAttachment: $input.css("background-attachment"), 1043 | backgroundClip: $input.css("background-clip"), 1044 | backgroundColor: $input.css("background-color"), 1045 | backgroundImage: $input.css("background-image"), 1046 | backgroundOrigin: $input.css("background-origin"), 1047 | backgroundPosition: $input.css("background-position"), 1048 | backgroundRepeat: $input.css("background-repeat"), 1049 | backgroundSize: $input.css("background-size") 1050 | }); 1051 | $input.data("ttAttrs", { 1052 | dir: $input.attr("dir"), 1053 | autocomplete: $input.attr("autocomplete"), 1054 | spellcheck: $input.attr("spellcheck"), 1055 | style: $input.attr("style") 1056 | }); 1057 | $input.addClass("tt-query").attr({ 1058 | autocomplete: "off", 1059 | spellcheck: false 1060 | }).css(css.query); 1061 | try { 1062 | !$input.attr("dir") && $input.attr("dir", "auto"); 1063 | } catch (e) {} 1064 | return $input.wrap($wrapper).parent().prepend($hint).append($dropdown); 1065 | } 1066 | function destroyDomStructure($node) { 1067 | var $input = $node.find(".tt-query"); 1068 | utils.each($input.data("ttAttrs"), function(key, val) { 1069 | utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); 1070 | }); 1071 | $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node); 1072 | $node.remove(); 1073 | } 1074 | }(); 1075 | (function() { 1076 | var cache = {}, viewKey = "ttView", methods; 1077 | methods = { 1078 | initialize: function(datasetDefs) { 1079 | var datasets; 1080 | datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ]; 1081 | if (datasetDefs.length === 0) { 1082 | $.error("no datasets provided"); 1083 | } 1084 | datasets = utils.map(datasetDefs, function(o) { 1085 | var dataset = cache[o.name] ? cache[o.name] : new Dataset(o); 1086 | if (o.name) { 1087 | cache[o.name] = dataset; 1088 | } 1089 | return dataset; 1090 | }); 1091 | return this.each(initialize); 1092 | function initialize() { 1093 | var $input = $(this), deferreds, eventBus = new EventBus({ 1094 | el: $input 1095 | }); 1096 | deferreds = utils.map(datasets, function(dataset) { 1097 | return dataset.initialize(); 1098 | }); 1099 | $input.data(viewKey, new TypeaheadView({ 1100 | input: $input, 1101 | eventBus: eventBus = new EventBus({ 1102 | el: $input 1103 | }), 1104 | datasets: datasets 1105 | })); 1106 | $.when.apply($, deferreds).always(function() { 1107 | utils.defer(function() { 1108 | eventBus.trigger("initialized"); 1109 | }); 1110 | }); 1111 | } 1112 | }, 1113 | destroy: function() { 1114 | return this.each(destroy); 1115 | function destroy() { 1116 | var $this = $(this), view = $this.data(viewKey); 1117 | if (view) { 1118 | view.destroy(); 1119 | $this.removeData(viewKey); 1120 | } 1121 | } 1122 | }, 1123 | setQuery: function(query) { 1124 | return this.each(setQuery); 1125 | function setQuery() { 1126 | var view = $(this).data(viewKey); 1127 | view && view.setQuery(query); 1128 | } 1129 | } 1130 | }; 1131 | jQuery.fn.typeahead = function(method) { 1132 | if (methods[method]) { 1133 | return methods[method].apply(this, [].slice.call(arguments, 1)); 1134 | } else { 1135 | return methods.initialize.apply(this, arguments); 1136 | } 1137 | }; 1138 | })(); 1139 | })(window.jQuery); -------------------------------------------------------------------------------- /app/assets/javascripts/bootstrap-editable.min.js: -------------------------------------------------------------------------------- 1 | /*! X-editable - v1.5.1 2 | * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery 3 | * http://github.com/vitalets/x-editable 4 | * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ 5 | !function(a){"use strict";var b=function(b,c){this.options=a.extend({},a.fn.editableform.defaults,c),this.$div=a(b),this.options.scope||(this.options.scope=this)};b.prototype={constructor:b,initInput:function(){this.input=this.options.input,this.value=this.input.str2value(this.options.value),this.input.prerender()},initTemplate:function(){this.$form=a(a.fn.editableform.template)},initButtons:function(){var b=this.$form.find(".editable-buttons");b.append(a.fn.editableform.buttons),"bottom"===this.options.showbuttons&&b.addClass("editable-buttons-bottom")},render:function(){this.$loading=a(a.fn.editableform.loading),this.$div.empty().append(this.$loading),this.initTemplate(),this.options.showbuttons?this.initButtons():this.$form.find(".editable-buttons").remove(),this.showLoading(),this.isSaving=!1,this.$div.triggerHandler("rendering"),this.initInput(),this.$form.find("div.editable-input").append(this.input.$tpl),this.$div.append(this.$form),a.when(this.input.render()).then(a.proxy(function(){if(this.options.showbuttons||this.input.autosubmit(),this.$form.find(".editable-cancel").click(a.proxy(this.cancel,this)),this.input.error)this.error(this.input.error),this.$form.find(".editable-submit").attr("disabled",!0),this.input.$input.attr("disabled",!0),this.$form.submit(function(a){a.preventDefault()});else{this.error(!1),this.input.$input.removeAttr("disabled"),this.$form.find(".editable-submit").removeAttr("disabled");var b=null===this.value||void 0===this.value||""===this.value?this.options.defaultValue:this.value;this.input.value2input(b),this.$form.submit(a.proxy(this.submit,this))}this.$div.triggerHandler("rendered"),this.showForm(),this.input.postrender&&this.input.postrender()},this))},cancel:function(){this.$div.triggerHandler("cancel")},showLoading:function(){var a,b;this.$form?(a=this.$form.outerWidth(),b=this.$form.outerHeight(),a&&this.$loading.width(a),b&&this.$loading.height(b),this.$form.hide()):(a=this.$loading.parent().width(),a&&this.$loading.width(a)),this.$loading.show()},showForm:function(a){this.$loading.hide(),this.$form.show(),a!==!1&&this.input.activate(),this.$div.triggerHandler("show")},error:function(b){var c,d=this.$form.find(".control-group"),e=this.$form.find(".editable-error-block");if(b===!1)d.removeClass(a.fn.editableform.errorGroupClass),e.removeClass(a.fn.editableform.errorBlockClass).empty().hide();else{if(b){c=(""+b).split("\n");for(var f=0;f").text(c[f]).html();b=c.join("
        ")}d.addClass(a.fn.editableform.errorGroupClass),e.addClass(a.fn.editableform.errorBlockClass).html(b).show()}},submit:function(b){b.stopPropagation(),b.preventDefault();var c=this.input.input2value(),d=this.validate(c);if("object"===a.type(d)&&void 0!==d.newValue){if(c=d.newValue,this.input.value2input(c),"string"==typeof d.msg)return this.error(d.msg),this.showForm(),void 0}else if(d)return this.error(d),this.showForm(),void 0;if(!this.options.savenochange&&this.input.value2str(c)==this.input.value2str(this.value))return this.$div.triggerHandler("nochange"),void 0;var e=this.input.value2submit(c);this.isSaving=!0,a.when(this.save(e)).done(a.proxy(function(a){this.isSaving=!1;var b="function"==typeof this.options.success?this.options.success.call(this.options.scope,a,c):null;return b===!1?(this.error(!1),this.showForm(!1),void 0):"string"==typeof b?(this.error(b),this.showForm(),void 0):(b&&"object"==typeof b&&b.hasOwnProperty("newValue")&&(c=b.newValue),this.error(!1),this.value=c,this.$div.triggerHandler("save",{newValue:c,submitValue:e,response:a}),void 0)},this)).fail(a.proxy(function(a){this.isSaving=!1;var b;b="function"==typeof this.options.error?this.options.error.call(this.options.scope,a,c):"string"==typeof a?a:a.responseText||a.statusText||"Unknown error!",this.error(b),this.showForm()},this))},save:function(b){this.options.pk=a.fn.editableutils.tryParseJson(this.options.pk,!0);var c,d="function"==typeof this.options.pk?this.options.pk.call(this.options.scope):this.options.pk,e=!!("function"==typeof this.options.url||this.options.url&&("always"===this.options.send||"auto"===this.options.send&&null!==d&&void 0!==d));return e?(this.showLoading(),c={name:this.options.name||"",value:b,pk:d},"function"==typeof this.options.params?c=this.options.params.call(this.options.scope,c):(this.options.params=a.fn.editableutils.tryParseJson(this.options.params,!0),a.extend(c,this.options.params)),"function"==typeof this.options.url?this.options.url.call(this.options.scope,c):a.ajax(a.extend({url:this.options.url,data:c,type:"POST"},this.options.ajaxOptions))):void 0},validate:function(a){return void 0===a&&(a=this.value),"function"==typeof this.options.validate?this.options.validate.call(this.options.scope,a):void 0},option:function(a,b){a in this.options&&(this.options[a]=b),"value"===a&&this.setValue(b)},setValue:function(a,b){this.value=b?this.input.str2value(a):a,this.$form&&this.$form.is(":visible")&&this.input.value2input(this.value)}},a.fn.editableform=function(c){var d=arguments;return this.each(function(){var e=a(this),f=e.data("editableform"),g="object"==typeof c&&c;f||e.data("editableform",f=new b(this,g)),"string"==typeof c&&f[c].apply(f,Array.prototype.slice.call(d,1))})},a.fn.editableform.Constructor=b,a.fn.editableform.defaults={type:"text",url:null,params:null,name:null,pk:null,value:null,defaultValue:null,send:"auto",validate:null,success:null,error:null,ajaxOptions:null,showbuttons:!0,scope:null,savenochange:!1},a.fn.editableform.template='
        ',a.fn.editableform.loading='
        ',a.fn.editableform.buttons='',a.fn.editableform.errorGroupClass=null,a.fn.editableform.errorBlockClass="editable-error",a.fn.editableform.engine="jquery"}(window.jQuery),function(a){"use strict";a.fn.editableutils={inherit:function(a,b){var c=function(){};c.prototype=b.prototype,a.prototype=new c,a.prototype.constructor=a,a.superclass=b.prototype},setCursorPosition:function(a,b){if(a.setSelectionRange)a.setSelectionRange(b,b);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",b),c.select()}},tryParseJson:function(a,b){if("string"==typeof a&&a.length&&a.match(/^[\{\[].*[\}\]]$/))if(b)try{a=new Function("return "+a)()}catch(c){}finally{return a}else a=new Function("return "+a)();return a},sliceObj:function(b,c,d){var e,f,g={};if(!a.isArray(c)||!c.length)return g;for(var h=0;h").text(b).html()},itemsByValue:function(b,c,d){if(!c||null===b)return[];if("function"!=typeof d){var e=d||"value";d=function(a){return a[e]}}var f=a.isArray(b),g=[],h=this;return a.each(c,function(c,e){if(e.children)g=g.concat(h.itemsByValue(b,e.children,d));else if(f)a.grep(b,function(a){return a==(e&&"object"==typeof e?d(e):e)}).length&&g.push(e);else{var i=e&&"object"==typeof e?d(e):e;b==i&&g.push(e)}}),g},createInput:function(b){var c,d,e,f=b.type;return"date"===f&&("inline"===b.mode?a.fn.editabletypes.datefield?f="datefield":a.fn.editabletypes.dateuifield&&(f="dateuifield"):a.fn.editabletypes.date?f="date":a.fn.editabletypes.dateui&&(f="dateui"),"date"!==f||a.fn.editabletypes.date||(f="combodate")),"datetime"===f&&"inline"===b.mode&&(f="datetimefield"),"wysihtml5"!==f||a.fn.editabletypes[f]||(f="textarea"),"function"==typeof a.fn.editabletypes[f]?(c=a.fn.editabletypes[f],d=this.sliceObj(b,this.objectKeys(c.defaults)),e=new c(d)):(a.error("Unknown type: "+f),!1)},supportsTransitions:function(){var a=document.body||document.documentElement,b=a.style,c="transition",d=["Moz","Webkit","Khtml","O","ms"];if("string"==typeof b[c])return!0;c=c.charAt(0).toUpperCase()+c.substr(1);for(var e=0;e"),this.tip().is(this.innerCss)?this.tip().append(this.$form):this.tip().find(this.innerCss).append(this.$form),this.renderForm()},hide:function(a){if(this.tip()&&this.tip().is(":visible")&&this.$element.hasClass("editable-open")){if(this.$form.data("editableform").isSaving)return this.delayedHide={reason:a},void 0;this.delayedHide=!1,this.$element.removeClass("editable-open"),this.innerHide(),this.$element.triggerHandler("hidden",a||"manual")}},innerShow:function(){},innerHide:function(){},toggle:function(a){this.container()&&this.tip()&&this.tip().is(":visible")?this.hide():this.show(a)},setPosition:function(){},save:function(a,b){this.$element.triggerHandler("save",b),this.hide("save")},option:function(a,b){this.options[a]=b,a in this.containerOptions?(this.containerOptions[a]=b,this.setContainerOption(a,b)):(this.formOptions[a]=b,this.$form&&this.$form.editableform("option",a,b))},setContainerOption:function(a,b){this.call("option",a,b)},destroy:function(){this.hide(),this.innerDestroy(),this.$element.off("destroyed"),this.$element.removeData("editableContainer")},innerDestroy:function(){},closeOthers:function(b){a(".editable-open").each(function(c,d){if(d!==b&&!a(d).find(b).length){var e=a(d),f=e.data("editableContainer");f&&("cancel"===f.options.onblur?e.data("editableContainer").hide("onblur"):"submit"===f.options.onblur&&e.data("editableContainer").tip().find("form").submit())}})},activate:function(){this.tip&&this.tip().is(":visible")&&this.$form&&this.$form.data("editableform").input.activate()}},a.fn.editableContainer=function(d){var e=arguments;return this.each(function(){var f=a(this),g="editableContainer",h=f.data(g),i="object"==typeof d&&d,j="inline"===i.mode?c:b;h||f.data(g,h=new j(this,i)),"string"==typeof d&&h[d].apply(h,Array.prototype.slice.call(e,1))})},a.fn.editableContainer.Popup=b,a.fn.editableContainer.Inline=c,a.fn.editableContainer.defaults={value:null,placement:"top",autohide:!0,onblur:"cancel",anim:!1,mode:"popup"},jQuery.event.special.destroyed={remove:function(a){a.handler&&a.handler()}}}(window.jQuery),function(a){"use strict";a.extend(a.fn.editableContainer.Inline.prototype,a.fn.editableContainer.Popup.prototype,{containerName:"editableform",innerCss:".editable-inline",containerClass:"editable-container editable-inline",initContainer:function(){this.$tip=a(""),this.options.anim||(this.options.anim=0)},splitOptions:function(){this.containerOptions={},this.formOptions=this.options},tip:function(){return this.$tip},innerShow:function(){this.$element.hide(),this.tip().insertAfter(this.$element).show()},innerHide:function(){this.$tip.hide(this.options.anim,a.proxy(function(){this.$element.show(),this.innerDestroy()},this))},innerDestroy:function(){this.tip()&&this.tip().empty().remove()}})}(window.jQuery),function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.editable.defaults,c,a.fn.editableutils.getConfigData(this.$element)),this.options.selector?this.initLive():this.init(),this.options.highlight&&!a.fn.editableutils.supportsTransitions()&&(this.options.highlight=!1)};b.prototype={constructor:b,init:function(){var b,c=!1;if(this.options.name=this.options.name||this.$element.attr("id"),this.options.scope=this.$element[0],this.input=a.fn.editableutils.createInput(this.options),this.input){switch(void 0===this.options.value||null===this.options.value?(this.value=this.input.html2value(a.trim(this.$element.html())),c=!0):(this.options.value=a.fn.editableutils.tryParseJson(this.options.value,!0),this.value="string"==typeof this.options.value?this.input.str2value(this.options.value):this.options.value),this.$element.addClass("editable"),"textarea"===this.input.type&&this.$element.addClass("editable-pre-wrapped"),"manual"!==this.options.toggle?(this.$element.addClass("editable-click"),this.$element.on(this.options.toggle+".editable",a.proxy(function(a){if(this.options.disabled||a.preventDefault(),"mouseenter"===this.options.toggle)this.show();else{var b="click"!==this.options.toggle;this.toggle(b)}},this))):this.$element.attr("tabindex",-1),"function"==typeof this.options.display&&(this.options.autotext="always"),this.options.autotext){case"always":b=!0;break;case"auto":b=!a.trim(this.$element.text()).length&&null!==this.value&&void 0!==this.value&&!c;break;default:b=!1}a.when(b?this.render():!0).then(a.proxy(function(){this.options.disabled?this.disable():this.enable(),this.$element.triggerHandler("init",this)},this))}},initLive:function(){var b=this.options.selector;this.options.selector=!1,this.options.autotext="never",this.$element.on(this.options.toggle+".editable",b,a.proxy(function(b){var c=a(b.target);c.data("editable")||(c.hasClass(this.options.emptyclass)&&c.empty(),c.editable(this.options).trigger(b))},this))},render:function(a){return this.options.display!==!1?this.input.value2htmlFinal?this.input.value2html(this.value,this.$element[0],this.options.display,a):"function"==typeof this.options.display?this.options.display.call(this.$element[0],this.value,a):this.input.value2html(this.value,this.$element[0]):void 0},enable:function(){this.options.disabled=!1,this.$element.removeClass("editable-disabled"),this.handleEmpty(this.isEmpty),"manual"!==this.options.toggle&&"-1"===this.$element.attr("tabindex")&&this.$element.removeAttr("tabindex")},disable:function(){this.options.disabled=!0,this.hide(),this.$element.addClass("editable-disabled"),this.handleEmpty(this.isEmpty),this.$element.attr("tabindex",-1)},toggleDisabled:function(){this.options.disabled?this.enable():this.disable()},option:function(b,c){return b&&"object"==typeof b?(a.each(b,a.proxy(function(b,c){this.option(a.trim(b),c)},this)),void 0):(this.options[b]=c,"disabled"===b?c?this.disable():this.enable():("value"===b&&this.setValue(c),this.container&&this.container.option(b,c),this.input.option&&this.input.option(b,c),void 0))},handleEmpty:function(b){this.options.display!==!1&&(this.isEmpty=void 0!==b?b:"function"==typeof this.input.isEmpty?this.input.isEmpty(this.$element):""===a.trim(this.$element.html()),this.options.disabled?this.isEmpty&&(this.$element.empty(),this.options.emptyclass&&this.$element.removeClass(this.options.emptyclass)):this.isEmpty?(this.$element.html(this.options.emptytext),this.options.emptyclass&&this.$element.addClass(this.options.emptyclass)):this.options.emptyclass&&this.$element.removeClass(this.options.emptyclass))},show:function(b){if(!this.options.disabled){if(this.container){if(this.container.tip().is(":visible"))return}else{var c=a.extend({},this.options,{value:this.value,input:this.input});this.$element.editableContainer(c),this.$element.on("save.internal",a.proxy(this.save,this)),this.container=this.$element.data("editableContainer")}this.container.show(b)}},hide:function(){this.container&&this.container.hide()},toggle:function(a){this.container&&this.container.tip().is(":visible")?this.hide():this.show(a)},save:function(a,b){if(this.options.unsavedclass){var c=!1;c=c||"function"==typeof this.options.url,c=c||this.options.display===!1,c=c||void 0!==b.response,c=c||this.options.savenochange&&this.input.value2str(this.value)!==this.input.value2str(b.newValue),c?this.$element.removeClass(this.options.unsavedclass):this.$element.addClass(this.options.unsavedclass)}if(this.options.highlight){var d=this.$element,e=d.css("background-color");d.css("background-color",this.options.highlight),setTimeout(function(){"transparent"===e&&(e=""),d.css("background-color",e),d.addClass("editable-bg-transition"),setTimeout(function(){d.removeClass("editable-bg-transition")},1700)},10)}this.setValue(b.newValue,!1,b.response)},validate:function(){return"function"==typeof this.options.validate?this.options.validate.call(this,this.value):void 0},setValue:function(b,c,d){this.value=c?this.input.str2value(b):b,this.container&&this.container.option("value",this.value),a.when(this.render(d)).then(a.proxy(function(){this.handleEmpty()},this))},activate:function(){this.container&&this.container.activate()},destroy:function(){this.disable(),this.container&&this.container.destroy(),this.input.destroy(),"manual"!==this.options.toggle&&(this.$element.removeClass("editable-click"),this.$element.off(this.options.toggle+".editable")),this.$element.off("save.internal"),this.$element.removeClass("editable editable-open editable-disabled"),this.$element.removeData("editable")}},a.fn.editable=function(c){var d={},e=arguments,f="editable";switch(c){case"validate":return this.each(function(){var b,c=a(this),e=c.data(f);e&&(b=e.validate())&&(d[e.options.name]=b)}),d;case"getValue":return 2===arguments.length&&arguments[1]===!0?d=this.eq(0).data(f).value:this.each(function(){var b=a(this),c=b.data(f);c&&void 0!==c.value&&null!==c.value&&(d[c.options.name]=c.input.value2submit(c.value))}),d;case"submit":var g=arguments[1]||{},h=this,i=this.editable("validate");if(a.isEmptyObject(i)){var j={};if(1===h.length){var k=h.data("editable"),l={name:k.options.name||"",value:k.input.value2submit(k.value),pk:"function"==typeof k.options.pk?k.options.pk.call(k.options.scope):k.options.pk};"function"==typeof k.options.params?l=k.options.params.call(k.options.scope,l):(k.options.params=a.fn.editableutils.tryParseJson(k.options.params,!0),a.extend(l,k.options.params)),j={url:k.options.url,data:l,type:"POST"},g.success=g.success||k.options.success,g.error=g.error||k.options.error}else{var m=this.editable("getValue");j={url:g.url,data:m,type:"POST"}}j.success="function"==typeof g.success?function(a){g.success.call(h,a,g)}:a.noop,j.error="function"==typeof g.error?function(){g.error.apply(h,arguments)}:a.noop,g.ajaxOptions&&a.extend(j,g.ajaxOptions),g.data&&a.extend(j.data,g.data),a.ajax(j)}else"function"==typeof g.error&&g.error.call(h,i);return this}return this.each(function(){var d=a(this),g=d.data(f),h="object"==typeof c&&c;return h&&h.selector?(g=new b(this,h),void 0):(g||d.data(f,g=new b(this,h)),"string"==typeof c&&g[c].apply(g,Array.prototype.slice.call(e,1)),void 0)})},a.fn.editable.defaults={type:"text",disabled:!1,toggle:"click",emptytext:"Empty",autotext:"auto",value:null,display:null,emptyclass:"editable-empty",unsavedclass:"editable-unsaved",selector:null,highlight:"#FFFF80"}}(window.jQuery),function(a){"use strict";a.fn.editabletypes={};var b=function(){};b.prototype={init:function(b,c,d){this.type=b,this.options=a.extend({},d,c)},prerender:function(){this.$tpl=a(this.options.tpl),this.$input=this.$tpl,this.$clear=null,this.error=null},render:function(){},value2html:function(b,c){a(c)[this.options.escape?"text":"html"](a.trim(b))},html2value:function(b){return a("
        ").html(b).text()},value2str:function(a){return a},str2value:function(a){return a},value2submit:function(a){return a},value2input:function(a){this.$input.val(a)},input2value:function(){return this.$input.val()},activate:function(){this.$input.is(":visible")&&this.$input.focus()},clear:function(){this.$input.val(null)},escape:function(b){return a("
        ").text(b).html()},autosubmit:function(){},destroy:function(){},setClass:function(){this.options.inputclass&&this.$input.addClass(this.options.inputclass)},setAttr:function(a){void 0!==this.options[a]&&null!==this.options[a]&&this.$input.attr(a,this.options[a])},option:function(a,b){this.options[a]=b}},b.defaults={tpl:"",inputclass:null,escape:!0,scope:null,showbuttons:!0},a.extend(a.fn.editabletypes,{abstractinput:b})}(window.jQuery),function(a){"use strict";var b=function(){};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){var b=a.Deferred();return this.error=null,this.onSourceReady(function(){this.renderList(),b.resolve()},function(){this.error=this.options.sourceError,b.resolve()}),b.promise()},html2value:function(){return null},value2html:function(b,c,d,e){var f=a.Deferred(),g=function(){"function"==typeof d?d.call(c,b,this.sourceData,e):this.value2htmlFinal(b,c),f.resolve()};return null===b?g.call(this):this.onSourceReady(g,function(){f.resolve()}),f.promise()},onSourceReady:function(b,c){var d;if(a.isFunction(this.options.source)?(d=this.options.source.call(this.options.scope),this.sourceData=null):d=this.options.source,this.options.sourceCache&&a.isArray(this.sourceData))return b.call(this),void 0;try{d=a.fn.editableutils.tryParseJson(d,!1)}catch(e){return c.call(this),void 0}if("string"==typeof d){if(this.options.sourceCache){var f,g=d;if(a(document).data(g)||a(document).data(g,{}),f=a(document).data(g),f.loading===!1&&f.sourceData)return this.sourceData=f.sourceData,this.doPrepend(),b.call(this),void 0;if(f.loading===!0)return f.callbacks.push(a.proxy(function(){this.sourceData=f.sourceData,this.doPrepend(),b.call(this)},this)),f.err_callbacks.push(a.proxy(c,this)),void 0;f.loading=!0,f.callbacks=[],f.err_callbacks=[]}var h=a.extend({url:d,type:"get",cache:!1,dataType:"json",success:a.proxy(function(d){f&&(f.loading=!1),this.sourceData=this.makeArray(d),a.isArray(this.sourceData)?(f&&(f.sourceData=this.sourceData,a.each(f.callbacks,function(){this.call()})),this.doPrepend(),b.call(this)):(c.call(this),f&&a.each(f.err_callbacks,function(){this.call()}))},this),error:a.proxy(function(){c.call(this),f&&(f.loading=!1,a.each(f.err_callbacks,function(){this.call()}))},this)},this.options.sourceOptions);a.ajax(h)}else this.sourceData=this.makeArray(d),a.isArray(this.sourceData)?(this.doPrepend(),b.call(this)):c.call(this)},doPrepend:function(){null!==this.options.prepend&&void 0!==this.options.prepend&&(a.isArray(this.prependData)||(a.isFunction(this.options.prepend)&&(this.options.prepend=this.options.prepend.call(this.options.scope)),this.options.prepend=a.fn.editableutils.tryParseJson(this.options.prepend,!0),"string"==typeof this.options.prepend&&(this.options.prepend={"":this.options.prepend}),this.prependData=this.makeArray(this.options.prepend)),a.isArray(this.prependData)&&a.isArray(this.sourceData)&&(this.sourceData=this.prependData.concat(this.sourceData)))},renderList:function(){},value2htmlFinal:function(){},makeArray:function(b){var c,d,e,f,g=[];if(!b||"string"==typeof b)return null;if(a.isArray(b)){f=function(a,b){return d={value:a,text:b},c++>=2?!1:void 0};for(var h=0;h1&&(e.children&&(e.children=this.makeArray(e.children)),g.push(e))):g.push({value:e,text:e})}else a.each(b,function(a,b){g.push({value:a,text:b})});return g},option:function(a,b){this.options[a]=b,"source"===a&&(this.sourceData=null),"prepend"===a&&(this.prependData=null)}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{source:null,prepend:!1,sourceError:"Error when loading list",sourceCache:!0,sourceOptions:null}),a.fn.editabletypes.list=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("text",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.renderClear(),this.setClass(),this.setAttr("placeholder")},activate:function(){this.$input.is(":visible")&&(this.$input.focus(),a.fn.editableutils.setCursorPosition(this.$input.get(0),this.$input.val().length),this.toggleClear&&this.toggleClear())},renderClear:function(){this.options.clear&&(this.$clear=a(''),this.$input.after(this.$clear).css("padding-right",24).keyup(a.proxy(function(b){if(!~a.inArray(b.keyCode,[40,38,9,13,27])){clearTimeout(this.t);var c=this;this.t=setTimeout(function(){c.toggleClear(b)},100)}},this)).parent().css("position","relative"),this.$clear.click(a.proxy(this.clear,this)))},postrender:function(){},toggleClear:function(){if(this.$clear){var a=this.$input.val().length,b=this.$clear.is(":visible");a&&!b&&this.$clear.show(),!a&&b&&this.$clear.hide()}},clear:function(){this.$clear.hide(),this.$input.val("").focus()}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'',placeholder:null,clear:!0}),a.fn.editabletypes.text=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("textarea",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.setClass(),this.setAttr("placeholder"),this.setAttr("rows"),this.$input.keydown(function(b){b.ctrlKey&&13===b.which&&a(this).closest("form").submit()})},activate:function(){a.fn.editabletypes.text.prototype.activate.call(this)}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:"",inputclass:"input-large",placeholder:null,rows:7}),a.fn.editabletypes.textarea=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("select",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.list),a.extend(b.prototype,{renderList:function(){this.$input.empty();var b=function(c,d){var e;if(a.isArray(d))for(var f=0;f",e),d[f].children))):(e.value=d[f].value,d[f].disabled&&(e.disabled=!0),c.append(a("