rec-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA512:
3
+ metadata.gz: 609036ff9cbd04e3bedf450eefb6ed1b60d9b75e838e36c67e8e7dcdaf749f1dc197a4542affe79a56bdddf2aeb4949fe090a895bd01e444f8ec04522e4b79b5
4
+ data.tar.gz: 433ecae74bd13bf3a77f1c5feb7b26a2d2d839cfd33634802e1185bed63582b0cc6842fcd4fd172e740c1ed0dfabd417aaa6b58a03bbaa6fa1ff942a2d4441e4
5
+ SHA1:
6
+ metadata.gz: d545d4c2fa2552209c2619b209861566c6ca5ff7
7
+ data.tar.gz: acb204b73ef1f77a33e54174f29f91a9bff62618
@@ -0,0 +1,17 @@
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
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in olay-rails.gemspec
4
+ gemspec
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2013 Casey Foster <c@sey.me>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # Rec for Rails
2
+
3
+ Places [Rec](http://orgsync.github.com/rec) into the Rails asset pipeline.
4
+
5
+ ## Versioning
6
+
7
+ This project uses [Semantic Versioning](http://semver.org/) and will account for the underlying Rec JavaScript library versioning.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'rec-rails', '~> 0.1'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ ## Usage
20
+
21
+ In your application.js:
22
+
23
+ //= require rec
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,2 @@
1
+ require 'rec/rails/version'
2
+ require 'rec/rails/engine'
@@ -0,0 +1,6 @@
1
+ module Rec
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Rec
2
+ VERSION = '0.0.1'
3
+ module Rails
4
+ VERSION = '0.0.1'
5
+ end
6
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rec/rails/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'rec-rails'
8
+ gem.version = Rec::Rails::VERSION
9
+ gem.summary = "Recommendations as you type."
10
+ gem.description = "Places Rec #{Rec::VERSION} in the Rails asset pipeline."
11
+ gem.homepage = 'https://github.com/orgsync/rec-rails'
12
+
13
+ gem.authors = ['Casey Foster']
14
+ gem.email = ['c@sey.me']
15
+
16
+ gem.license = 'MIT'
17
+
18
+ gem.files = `git ls-files`.split($/)
19
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
+ gem.require_paths = ['lib']
22
+
23
+ gem.add_dependency 'rails', '>= 3.1'
24
+ end
@@ -0,0 +1,352 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ // Store a local reference to jQuery and Underscorea.
5
+ var $ = window.jQuery;
6
+ var _ = window._;
7
+
8
+ var keyDirMap = {'38': 'prev', '40': 'next'};
9
+ var ctrlKeyDirMap = {'80': 'prev', '78': 'next'};
10
+
11
+ // Delay a callback `n` ms or invoke immediately if 0.
12
+ var delay = function (cb, n) { return !n ? cb() : _.delay(cb, n); };
13
+
14
+ // Direct event bindings to `$el`.
15
+ var directEvents = {
16
+ mouseover: function () { this._hover = true; },
17
+ mouseout: function () {
18
+ this._hover = false;
19
+ _.defer(_.bind(function () {
20
+ if (!this._focus && !this._hover) this.hide();
21
+ }, this));
22
+ }
23
+ };
24
+
25
+ // Delegated event bindings to children of `$el`.
26
+ var delegatedEvents = {
27
+
28
+ // Delegated events for the input area.
29
+ '.js-rec-input': {
30
+
31
+ // Update the `_focus` flag and show the latest query results.
32
+ focus: function (ev) {
33
+ this._focus = true;
34
+ this.query($(ev.currentTarget).val()).show();
35
+ },
36
+
37
+ // Check for the up/down keys and enter key, and finally submit a new
38
+ // query.
39
+ keydown: function (ev) {
40
+ var key = ev.which;
41
+ var dir = keyDirMap[key] || (ev.ctrlKey && ctrlKeyDirMap[key]);
42
+ if (dir) {
43
+ this[dir]();
44
+ return ev.preventDefault();
45
+ }
46
+ if (key === 13) {
47
+ var selected = this.$el.find('.js-rec-selected')[0];
48
+ if (selected && selected !== ev.currentTarget) {
49
+ selected.click();
50
+ return ev.preventDefault();
51
+ }
52
+ }
53
+ var $input = $(ev.currentTarget);
54
+ if (key === 27) {
55
+ $input.blur();
56
+ this.hide();
57
+ return ev.preventDefault();
58
+ }
59
+ _.defer(_.bind(function () { this.query($input.val()); }, this));
60
+ },
61
+
62
+ // Hide the results when focus is lost.
63
+ blur: function () {
64
+ this._focus = false;
65
+ if (!this._hover) this.hide();
66
+ }
67
+ },
68
+
69
+ // Delegated events for invidiual results.
70
+ '.js-rec-selectable': {
71
+
72
+ // Select the result that is being hovered over.
73
+ mouseover: function (ev) { this.select($(ev.currentTarget)); },
74
+
75
+ // Optionally clear and hide the input and results on result click.
76
+ click: function (ev) {
77
+ var $input = this.$el.find('.js-rec-input');
78
+ if (ev.currentTarget === $input[0]) return;
79
+ if (this.clearOnClick) $input.val('').focus();
80
+ if (this.hideOnClick) {
81
+ $input.blur();
82
+ this.hide();
83
+ }
84
+ }
85
+ }
86
+ };
87
+
88
+
89
+ // Create the `Rec` constructor.
90
+ //
91
+ // ```js
92
+ // var rec = new Rec('#my-rec-container', {
93
+ // url: '/search',
94
+ // template: jst['search-result']
95
+ // });
96
+ // ```
97
+ var Rec = window.Rec = function (el, options) {
98
+
99
+ // Define direct and delegated event hashes unique to this instance. This
100
+ // will allow a clean unbinding of these events without worry about any
101
+ // event collisions.
102
+ this._directEvents = {};
103
+ _.each(directEvents, function (cb, key) {
104
+ this._directEvents[key] = _.bind(cb, this);
105
+ }, this);
106
+ this._delegatedEvents = {};
107
+ _.each(delegatedEvents, function (evs, selector) {
108
+ var bound = this._delegatedEvents[selector] = {};
109
+ _.each(evs, function (cb, name) {
110
+ bound[name] = _.bind(cb, this);
111
+ }, this);
112
+ }, this);
113
+
114
+ // Create a cache object to store results.
115
+ this._cached = {};
116
+
117
+ // Track concurrent fetches for showing/hiding the loading spinner.
118
+ this._fetchQueue = 0;
119
+
120
+ // Set the container element.
121
+ this.setElement(el);
122
+
123
+ // Extend the instance with its options.
124
+ _.extend(this, options);
125
+ };
126
+
127
+ // Define `prototype` properties and methods for `Rec`.
128
+ _.extend(Rec.prototype, {
129
+
130
+ // How long since the last keystroke should rec wait to call `fetch`?
131
+ delay: 250,
132
+
133
+ // How many results should be displayed? `0` means as many as are returned
134
+ // by `fetch`.
135
+ limit: 0,
136
+
137
+ // Should results be cached? Any cross-session caching will need to be
138
+ // handled by your `fetch` method, but in-memory caching is free with rec.
139
+ cache: true,
140
+
141
+ // Should the second selectable item be automatically selected? In other
142
+ // words, will hitting return fire the first recommended result or the broad
143
+ // result?
144
+ selectSecond: false,
145
+
146
+ // Should the results be hidden and focus blurred when a result is clicked?
147
+ hideOnClick: false,
148
+
149
+ // Should the input be cleared when a result is clicked?
150
+ clearOnClick: true,
151
+
152
+ // The query string key that will be used with the default `fetch` method.
153
+ queryKey: 'q',
154
+
155
+ // Should rec attempt to fetch query results for an empty string?
156
+ fetchNothing: false,
157
+
158
+ // Override this template functions to return your labels and results the
159
+ // way you want them. If you are not using the `groupBy` option,
160
+ // `labelTemplate` will be ignored.
161
+ labelTemplate: function (label) { return '<div>' + label + '</div>'; },
162
+ resultTemplate: function (result) { return '<div>' + result + '</div>'; },
163
+
164
+ // Parse the query before sending it to `fetch`. This allows you to clean up
165
+ // bad characters, extra spaces, etc...
166
+ parse: function (q) {
167
+ return q.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '').toLowerCase();
168
+ },
169
+
170
+ // `filter` works alongside `cache` to intelligently predict possible
171
+ // results while waiting for a server request to come back. Override this
172
+ // function with logic that best matches your server's filter logic.
173
+ filter: function (q, result) {
174
+ var str = _.values(result).join(' ').toLowerCase();
175
+ return _.every(q.split(' '), function (w) { return ~str.indexOf(w); });
176
+ },
177
+
178
+ // The default `fetch` is designed for simple, JSON AJAX requests. Override
179
+ // thid function to suite your application's needs.
180
+ fetch: function (q, cb) {
181
+ var options = this.fetchOptions || {};
182
+ (options.data || (options.data = {}))[this.queryKey] = q;
183
+ return $.ajax(_.extend({
184
+ type: 'get',
185
+ dataType: 'json',
186
+ success: function (results) { cb(null, results); },
187
+ error: function (xhr) { cb(xhr.responseText); }
188
+ }, options));
189
+ },
190
+
191
+ query: function (q) {
192
+
193
+ // Parse the raw query string.
194
+ q = this.parse(q);
195
+
196
+ // Compare to the last query and store so we don't waste any time.
197
+ if (q === this._lastQ) return this;
198
+ this._lastQ = q;
199
+
200
+ // Clear the old `fetch` request if one was about to fire.
201
+ if (this._timeout) {
202
+ clearTimeout(this._timeout);
203
+ this._timeout = null;
204
+ if (!--this._fetchQueue) this.$el.removeClass('js-rec-loading');
205
+ }
206
+
207
+ // Looked for a cache result before trying to load.
208
+ if (!this._getCached(q) && (q || this.fetchEmptyQuery)) {
209
+
210
+ // Add the loading class.
211
+ ++this._fetchQueue;
212
+ this.$el.addClass('js-rec-loading');
213
+
214
+ // Start a timeout to `fetch`
215
+ var self = this;
216
+ this._timeout = delay(function () {
217
+ self._timeout = null;
218
+ self.fetch(q, function (er, results, temp) {
219
+ if (!temp && !--self._fetchQueue) {
220
+ self.$el.removeClass('js-rec-loading');
221
+ }
222
+ self._setCached(q, results);
223
+ self._render();
224
+ });
225
+ }, this.delay);
226
+ }
227
+ return this._render();
228
+ },
229
+
230
+ // Set the input element the rec will watch.
231
+ setElement: function (el) {
232
+ if (this.$el) this._unbind();
233
+ this.$el = el instanceof $ ? el : $(el);
234
+ return this._bind();
235
+ },
236
+
237
+ show: function () {
238
+ this.$el.addClass('js-rec-active');
239
+ this.select();
240
+ return this;
241
+ },
242
+
243
+ hide: function () {
244
+ this.$el.removeClass('js-rec-active');
245
+ return this;
246
+ },
247
+
248
+ select: function ($el) {
249
+ this.$el.find('.js-rec-selected').removeClass('js-rec-selected');
250
+ if (!$el) {
251
+ var $selectable = this.$el.find('.js-rec-selectable:visible');
252
+ var i = Math.min(this.selectSecond ? 1 : 0, $selectable.length - 1);
253
+ $el = $selectable.eq(i);
254
+ }
255
+ $el.addClass('js-rec-selected');
256
+ return this;
257
+ },
258
+
259
+ _dir: function (step) {
260
+ var $selectable = this.$el.find('.js-rec-selectable:visible');
261
+ var $selected = $selectable.filter('.js-rec-selected');
262
+ var i = _.indexOf($selectable, $selected[0]);
263
+ if (i === -1) return this.select($selectable.first());
264
+ i += step;
265
+ if (i === -1 || i === $selectable.length) return this;
266
+ return this.select($selectable.eq(i));
267
+ },
268
+
269
+ prev: function () { this._dir(-1); },
270
+
271
+ next: function () { this._dir(1); },
272
+
273
+ _getCached: function (q) {
274
+ return this.cache && this._cached[q];
275
+ },
276
+
277
+ _setCached: function (q, results) {
278
+ this._cached[q] = results;
279
+ return this;
280
+ },
281
+
282
+ _getFiltered: function (q) {
283
+ if (!this.filter) return null;
284
+ var cached;
285
+ for (var i = q.length - 1; i > 0; --i) {
286
+ if (cached = this._getCached(q.slice(0, i))) break;
287
+ }
288
+ if (!cached) return null;
289
+ var filter = _.bind(this.filter, this, q);
290
+ var matches = _.filter(cached, filter);
291
+ return _.size(matches) ? matches : null;
292
+ },
293
+
294
+
295
+ // Bind (or unbind) delegated events from a multi-level object.
296
+ _bind: function (unbind) {
297
+ var $el = this.$el;
298
+ var method = unbind ? 'off': 'on';
299
+ $el[method](this._directEvents);
300
+ _.each(this._delegatedEvents, function (evs, selector) {
301
+ _.each(evs, function (cb, name) { $el[method](name, selector, cb); });
302
+ });
303
+ return this;
304
+ },
305
+
306
+ // Inverse `_bind`.
307
+ _unbind: function () { return this._bind(true); },
308
+
309
+ // Build the elements for the most recent query.
310
+ _render: function () {
311
+ var q = this._lastQ;
312
+ var results = this._getCached(q) || this._getFiltered(q);
313
+ this.$el[(q === '' ? 'add' : 'remove') + 'Class']('js-rec-nothing')
314
+ .removeClass('js-rec-no-results')
315
+ .find('.js-rec-result, .js-rec-label').remove();
316
+ if (results) {
317
+ var $results = this.$el.find('.js-rec-results');
318
+ if (results.length) {
319
+ results = this.limit ? _.first(results, this.limit) : results;
320
+ var limit = results.length;
321
+ var count = 0;
322
+ results = _.groupBy(results, this.groupBy || 'undefined');
323
+ _.each(results, function (results, label) {
324
+ if (count === limit) return;
325
+ if (label !== 'undefined') {
326
+ $results.append(this._renderLabel(label));
327
+ }
328
+ _.each(results, function (result) {
329
+ if (count === limit) return;
330
+ $results.append(this._renderResult(result));
331
+ ++count;
332
+ }, this);
333
+ }, this);
334
+ } else {
335
+ this.$el.addClass('js-rec-no-results');
336
+ }
337
+ }
338
+ return this.select();
339
+ },
340
+
341
+ _renderLabel: function (label) {
342
+ var el = this.labelTemplate(label);
343
+ return (el instanceof $ ? el : $(el)).addClass('js-rec-label');
344
+ },
345
+
346
+ _renderResult: function (result) {
347
+ var el = this.resultTemplate(result);
348
+ return (el instanceof $ ? el : $(el))
349
+ .addClass('js-rec-result js-rec-selectable');
350
+ }
351
+ });
352
+ })();
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rec-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Casey Foster
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2013-04-01 00:00:00 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ version_requirements: &id001 !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: "3.1"
20
+ prerelease: false
21
+ name: rails
22
+ requirement: *id001
23
+ type: :runtime
24
+ description: Places Rec 0.0.1 in the Rails asset pipeline.
25
+ email:
26
+ - c@sey.me
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - .gitignore
35
+ - Gemfile
36
+ - LICENSE.txt
37
+ - README.md
38
+ - Rakefile
39
+ - lib/rec-rails.rb
40
+ - lib/rec/rails/engine.rb
41
+ - lib/rec/rails/version.rb
42
+ - olay-rails.gemspec
43
+ - vendor/assets/javascripts/rec.js
44
+ homepage: https://github.com/orgsync/rec-rails
45
+ licenses:
46
+ - MIT
47
+ metadata: {}
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - &id002
57
+ - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - *id002
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 2.0.0
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Recommendations as you type.
70
+ test_files: []
71
+