backbone_rails_extensions 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2e1cae0d9815728d45601caf3e5db499706ebd00
4
+ data.tar.gz: c589d820ef317fbd5b73539085c078797d165144
5
+ SHA512:
6
+ metadata.gz: d3aefb0bd3fdaaefd9f0e9bd42e63cf54b35822cb0781c7a453fd70a277da10ab4bcc4f276064fce16ddebe5d289315522ca72fa21d0c332378b1410055c7420
7
+ data.tar.gz: 63c49bd6f35ba5b324e79438347df43dd90d7107502471fedbeb8ec9bdd1e1b56369413d5a781f08f21817633a49e275130a3ad9ed2a8199ebf52b5c7b039d51
data/.gitignore ADDED
@@ -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 backbone-rails-extensions.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Coroutine LLC
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.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # BackboneRailsExtensions
2
+
3
+ This gem provides a set of common extensions to Backbone.js. These extensions include simple collection views, paginated collection views, searching, and loading indicators.
4
+
5
+ All files are included in the asset pipeline by default. To leverage the files in your project, simply reference the components in your manifest files.
6
+
7
+ ## Dependencies
8
+
9
+ The project has a dependency on Spin.js. Appropriate files are included in the vendor directory.
10
+
11
+ ## Versioning
12
+
13
+ Major and minor version numbers are pegged to Backbone.js. Patch versions are specific
14
+ to this project.
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ gem 'backbone-rails-extensions', :git => 'git@github.com:coroutine/backbone-rails-extensions.git',
21
+ :tag => '0.9.0'
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install backbone-rails-extensions
30
+
31
+ ## Usage
32
+
33
+ TODO: Write usage instructions here
34
+
35
+ ## Contributing
36
+
37
+ 1. Fork it
38
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
39
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
40
+ 4. Push to the branch (`git push origin my-new-feature`)
41
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'backbone_rails_extensions/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "backbone_rails_extensions"
8
+ gem.version = BackboneRailsExtensions::VERSION
9
+ gem.authors = ["Coroutine", "Tim Lowrimore", "John Dugan"]
10
+ gem.email = ["gems@coroutine.com"]
11
+ gem.description = %q{This gem provides a set of Backbone.js extensions commonly used in Coroutine projects. These extensions include simple collection views, paginated collection views, searching, and loading indicators.}
12
+ gem.summary = %q{This gem provides a set of Backbone.js extensions commonly used in Coroutine projects.}
13
+ gem.homepage = "https://github.com/coroutine/backbone-rails-extensions"
14
+ gem.licenses = ['MIT']
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,35 @@
1
+ (function($) {
2
+ var methods = {
3
+ setValue: function(path, value, obj) {
4
+ if(path.length) {
5
+ var attr = path.shift();
6
+ if(attr) {
7
+ obj[attr] = methods.setValue(path, value, obj[attr] || {});
8
+ return obj;
9
+ } else {
10
+ if(obj.push) {
11
+ obj.push(value);
12
+ return obj;
13
+ } else {
14
+ return [value];
15
+ }
16
+ }
17
+ } else {
18
+ return value;
19
+ }
20
+ }
21
+ };
22
+
23
+ $.fn.serializeObject = function() {
24
+ var obj = {},
25
+ params = this.serializeArray(),
26
+ path = null;
27
+
28
+ $.each(params, function() {
29
+ path = this.name.replace(/\]/g, "").split(/\[/);
30
+ methods.setValue(path, this.value, obj);
31
+ });
32
+
33
+ return obj;
34
+ };
35
+ })(jQuery);
@@ -0,0 +1,4 @@
1
+ var List = {
2
+ Router: {},
3
+ View: {}
4
+ }
@@ -0,0 +1,43 @@
1
+ //= require list/namespace
2
+
3
+ List.Router.Base = Backbone.Router.extend({
4
+ routes: {
5
+ "": "index",
6
+ "page/:page": "_page",
7
+ "query/*query/within/*within": "_search",
8
+ "page/:page/query/*query/within/*within": "_searchWithPage"
9
+ },
10
+
11
+ currentParams: {},
12
+
13
+ initialize: function() {
14
+ this.initializeView();
15
+ },
16
+
17
+ initializeView: function() {
18
+ this._raiseUnimplementedError();
19
+ },
20
+
21
+ index: function(parameters) {
22
+ this._raiseUnimplementedError();
23
+ },
24
+
25
+ _raiseUnimplementedError: function() {
26
+ throw "method not implemented";
27
+ },
28
+
29
+ _search: function(query, within) {
30
+ this._searchWithPage(1, query, within);
31
+ },
32
+
33
+ _searchWithPage: function(page, query, within) {
34
+ this.index({
35
+ query: query,
36
+ within: within,
37
+ page: page || 1 });
38
+ },
39
+
40
+ _page: function(page) {
41
+ this.index({ page: page || 1 });
42
+ }
43
+ });
@@ -0,0 +1,27 @@
1
+ //= require list/namespace
2
+
3
+ List.View.CollectionView = Backbone.View.extend({
4
+ setCollection: function(collection) {
5
+ if(collection !== this.collection) {
6
+ if(this.collection) {
7
+ this.collection.off("add remove reset", this.render, this);
8
+ this.collection.off("error", this.renderError, this);
9
+ }
10
+
11
+ this.collection = collection;
12
+ if(this.collection) {
13
+ this.collection.on("add remove reset", this.render, this);
14
+ this.collection.on("error", this.renderError, this);
15
+ this.render();
16
+ }
17
+ }
18
+ },
19
+
20
+ render: function() {
21
+ throw "method not implemented";
22
+ },
23
+
24
+ renderError: function(data, response) {
25
+ alert("An error occurred while fetching the records from the server.");
26
+ }
27
+ });
@@ -0,0 +1,30 @@
1
+ //= require list/namespace
2
+
3
+ List.View.Search = Backbone.View.extend({
4
+ events: {
5
+ "keydown #search_field_name": "_onNameChanged",
6
+ "change #search_field_type": "_onTypeChanged"
7
+ },
8
+
9
+ // handlers
10
+ _onNameChanged: function(evt) {
11
+ if (evt.keyCode == 13) { // suppress enter key
12
+ evt.preventDefault();
13
+ }
14
+ this._doSearch();
15
+ },
16
+
17
+ _onTypeChanged: function(evt) {
18
+ this._doSearch();
19
+ },
20
+
21
+ // actions
22
+ _doSearch: _.debounce(function() {
23
+ var query = escape(this.$("#search_field_name").val()),
24
+ within = this.$("#search_field_type").val(),
25
+ route = [ "query", query, "within", within ].join("/");
26
+
27
+ Backbone.history.navigate(route, { trigger: true });
28
+ }, 500)
29
+
30
+ });
@@ -0,0 +1,56 @@
1
+ //= require list/namespace
2
+ //= require list/views/collection_view
3
+
4
+ List.View.SimpleCollectionView = List.View.CollectionView.extend({
5
+
6
+ //-----------------------------------------------------
7
+ // initialization
8
+ //-----------------------------------------------------
9
+
10
+ initialize: function() {
11
+ this.beforeInitialize();
12
+
13
+ if(!this.getItemClass) {
14
+ throw "The method 'getItemClass' was expected, but was not found. \
15
+ Make sure you've specified this method in your subclasses of \
16
+ List.View.SimpleCollectionView"
17
+ }
18
+ this.itemViews = [];
19
+
20
+ _.bindAll(this, "render", "_appendItemToView");
21
+
22
+ this.afterInitialize();
23
+ },
24
+
25
+ render: function() {
26
+ this._clearItemList();
27
+ if(this.collection) {
28
+ _.each(this.collection.models, this._appendItemToView);
29
+ }
30
+ },
31
+
32
+
33
+ //-----------------------------------------------------
34
+ // public methods
35
+ //-----------------------------------------------------
36
+
37
+ // callbacks: override if needed
38
+ afterInitialize: function() {},
39
+ beforeInitialize: function() {},
40
+
41
+
42
+ //-----------------------------------------------------
43
+ // private methods
44
+ //-----------------------------------------------------
45
+
46
+ _appendItemToView: function(item) {
47
+ var itemClass = this.getItemClass(),
48
+ itemView = new itemClass({ model: item });
49
+
50
+ this.itemViews.push(itemView);
51
+ this.$el.append(itemView.el);
52
+ },
53
+ _clearItemList: function() {
54
+ while(this.itemViews.length) { this.itemViews.pop().remove(); }
55
+ }
56
+ });
@@ -0,0 +1,4 @@
1
+ var Loader = {
2
+ View: {},
3
+ Util: {}
4
+ };
@@ -0,0 +1,24 @@
1
+ //= require loader/namespace
2
+
3
+ Loader.customizeBackboneSync = _.once(function() {
4
+ var customSync = function(method, model, options) {
5
+ var success = options.success,
6
+ error = options.error,
7
+ showLoader = _.has(options, "showLoader") ? options.showLoader : true;
8
+
9
+ options.success = function() {
10
+ success.apply(this, arguments);
11
+ Loader.View.Base.hide();
12
+ };
13
+
14
+ options.error = function() {
15
+ error.apply(this, arguments);
16
+ Loader.View.Base.hide();
17
+ };
18
+
19
+ if(showLoader) Loader.View.Base.show();
20
+ Backbone.sync.call(this, method, model, options);
21
+ };
22
+
23
+ Backbone.Model.prototype.sync = Backbone.Collection.prototype.sync = customSync;
24
+ })
@@ -0,0 +1,26 @@
1
+ //= require loader/namespace
2
+
3
+ Loader.Util.CountingLatch = function(initialCount) {
4
+ this.count = initialCount || 0;
5
+ }
6
+
7
+ _.extend(Loader.Util.CountingLatch.prototype, Backbone.Events, {
8
+ countUp: function() {
9
+ this.count++;
10
+ this._check();
11
+ },
12
+
13
+ countDown: function() {
14
+ // ensure the count never goes below zero.
15
+ this.count = Math.max(0, this.count - 1);
16
+ this._check();
17
+ },
18
+
19
+ _check: function() {
20
+ if(this.count) {
21
+ this.trigger("latch:counting");
22
+ } else {
23
+ this.trigger("latch:complete");
24
+ }
25
+ }
26
+ });
@@ -0,0 +1,41 @@
1
+ //= require jquery.spin
2
+ //= require loader/namespace
3
+ //= require loader/utils/counting_latch
4
+
5
+ Loader.View.Base = {
6
+ CONFIG: {
7
+ lines: 12,
8
+ length: 3,
9
+ width: 4,
10
+ radius: 15,
11
+ trail: 34,
12
+ speed: 1.3,
13
+ shadow: true,
14
+ hwaccel: true,
15
+ color: "#FFF"
16
+ },
17
+
18
+ initialize: _.once(function() {
19
+ _.bindAll(this, "show", "hide");
20
+
21
+ this.latch = new Loader.Util.CountingLatch();
22
+ this.latch.on("latch:counting", this._showLoaders, this);
23
+ this.latch.on("latch:complete", this._hideLoaders, this);
24
+ }),
25
+
26
+ show: function() {
27
+ this.latch.countUp();
28
+ },
29
+
30
+ hide: function() {
31
+ this.latch.countDown();
32
+ },
33
+
34
+ _showLoaders: function() {
35
+ $("#loading-indicator").fadeIn(50).spin(this.CONFIG);
36
+ },
37
+
38
+ _hideLoaders: function() {
39
+ $("#loading-indicator").fadeOut(50).spin(false);
40
+ }
41
+ };
@@ -0,0 +1,24 @@
1
+ //= require pagination/namespace
2
+ //= require pagination/models/pagination
3
+
4
+ Pagination.Collection.PaginatedCollection = Backbone.Collection.extend({
5
+ pagination: new Pagination.Model.Pagination(),
6
+
7
+ initialize: function() {
8
+ this.on("destroy", this._decrement, this);
9
+ this.on("add", this._increment, this);
10
+ },
11
+
12
+ parse: function(response) {
13
+ this.pagination.set(response.pagination);
14
+ return response;
15
+ },
16
+
17
+ _decrement: function() {
18
+ this.pagination.decrement("totalCount");
19
+ },
20
+
21
+ _increment: function() {
22
+ this.pagination.increment("totalCount");
23
+ }
24
+ });
@@ -0,0 +1,57 @@
1
+ //= require pagination/namespace
2
+
3
+ Pagination.Model.Pagination = Backbone.Model.extend({
4
+ defaults: {
5
+ "totalCount": 0,
6
+ "pageCount": 0,
7
+ "numPages": 0,
8
+ "currentPage": 1,
9
+ "offsetValue": 0,
10
+ "maxPageLinks": 10
11
+ },
12
+
13
+ validate: function(attrs) {
14
+ var nonNumericAttrs = _(attrs).reduce(function(m, v, k) {
15
+ if(!_(v).isNumber()) {
16
+ m.push("'" + k + "' must be a real number.");
17
+ }
18
+ return m;
19
+ }, []);
20
+
21
+ if(nonNumericAttrs.length) {
22
+ return nonNumericAttrs.join("\n");
23
+ }
24
+ },
25
+
26
+ increment: function(field) {
27
+ var incremented = this.get(field) + 1,
28
+ args = {};
29
+
30
+ args[field] = incremented;
31
+ this.set(args);
32
+ },
33
+
34
+ decrement: function(field) {
35
+ var decremented = Math.max(0, this.get(field) - 1),
36
+ args = {};
37
+
38
+ args[field] = decremented;
39
+ this.set(args);
40
+ },
41
+
42
+ isFirstPage: function() {
43
+ return this.get("currentPage") == 1;
44
+ },
45
+
46
+ isLastPage: function() {
47
+ return this.get("currentPage") >= this.get("numPages");
48
+ },
49
+
50
+ nextPage: function() {
51
+ return this.get("currentPage") + 1;
52
+ },
53
+
54
+ prevPage: function() {
55
+ return Math.max(1, this.get("currentPage") - 1);
56
+ }
57
+ });
@@ -0,0 +1,5 @@
1
+ var Pagination = {
2
+ View: {},
3
+ Model: {},
4
+ Collection: {}
5
+ };
@@ -0,0 +1,6 @@
1
+ <div class="pagination_totals"></div>
2
+ <nav class="pagination_links">
3
+ <a href="#prev" class="prev">&laquo; Previous</a>
4
+ <span class="page_numbers"></span>
5
+ <a href="#next" class="next">Next &raquo;</a>
6
+ </nav>
@@ -0,0 +1,164 @@
1
+ //= require pagination/namespace
2
+ //= require pagination/templates/pagination
3
+
4
+ Pagination.View.Pagination = Backbone.View.extend({
5
+ tagName: "div",
6
+ infoTemplate: _.template("<%= start %> - <%= end %> of <%= total %>"),
7
+ hashPartExpr: /(page\/\d+)/,
8
+
9
+ events: {
10
+ "click .prev": "fetchPreviousPage",
11
+ "click .next": "fetchNextPage",
12
+ "click .page_numbers a": "fetchPage"
13
+ },
14
+
15
+ initialize: function(options) {
16
+ if(!this.model) {
17
+ throw "No pagination model was specified.";
18
+ }
19
+
20
+ if(_(options.infoTemplate).isFunction()) {
21
+ this.infoTemplate = options.infoTemplate;
22
+ }
23
+
24
+ this.model.on("change", function() {
25
+ this.renderInfo();
26
+ this.renderLinks();
27
+ }, this);
28
+
29
+ this.render();
30
+ },
31
+
32
+ render: function() {
33
+ this.$el.append(JST["pagination/templates/pagination"]());
34
+ this.renderInfo();
35
+ this.renderLinks();
36
+ },
37
+
38
+ renderInfo: function() {
39
+ var model = this.model,
40
+ total = model.get("totalCount"),
41
+ pageCount = model.get("pageCount"),
42
+ numPages = model.get("numPages"),
43
+ currentPage = model.get("currentPage"),
44
+ offsetValue = model.get("offsetValue"),
45
+ info = this.infoTemplate({
46
+ start: offsetValue + 1,
47
+ end: pageCount + offsetValue,
48
+ total: total
49
+ });
50
+
51
+ this.$(".pagination_totals").html(info);
52
+ },
53
+
54
+ renderLinks: function() {
55
+ this.renderPreviousAndNextLinks();
56
+ this.renderPageLinks();
57
+ },
58
+
59
+ renderPreviousAndNextLinks: function() {
60
+ var isFirstPage = this.model.isFirstPage(),
61
+ isLastPage = this.model.isLastPage(),
62
+ linkPrev = this.$(".prev"),
63
+ linkNext = this.$(".next");
64
+
65
+ if(isFirstPage) {
66
+ linkPrev.addClass("disabled");
67
+ } else {
68
+ linkPrev.removeClass("disabled");
69
+ }
70
+
71
+ if(isLastPage) {
72
+ linkNext.addClass("disabled");
73
+ } else {
74
+ linkNext.removeClass("disabled");
75
+ }
76
+ },
77
+
78
+ renderPageLinks: function() {
79
+ var numPages = this.model.get("numPages"),
80
+ maxPageLinks = Math.min(this.model.get("maxPageLinks"), numPages),
81
+ loopLinks = Math.max(maxPageLinks - 2, 0),
82
+ currentPage = this.model.get("currentPage"),
83
+ pageNumbers = this.$(".page_numbers");
84
+
85
+ // clear the previous page numbers
86
+ pageNumbers.empty();
87
+
88
+ // Add the first page
89
+ pageNumbers.append(this._createPageLink(1, currentPage));
90
+
91
+ if(numPages > 1) {
92
+ var maxOffset = Math.max(currentPage - loopLinks, 0) + maxPageLinks,
93
+ minOffset = Math.min(maxOffset, numPages) - maxPageLinks;
94
+
95
+ // if we have an offset value greater than zero, render an ellipsis
96
+ // after the page-1 link.
97
+ if(minOffset) { pageNumbers.append("&hellip;"); }
98
+
99
+ // render page links between the first and last page.
100
+ _(loopLinks).times(_.bind(function(i) {
101
+ pageNumbers.append(
102
+ this._createPageLink(i + 2 + minOffset, currentPage));
103
+ }, this));
104
+
105
+ // If the tail of our 'in-between' links is not within
106
+ // range of the last page link, such that it creates a
107
+ // natural numeric series upto the last page link, render
108
+ // an ellipsis.
109
+ if(maxOffset < numPages) {
110
+ pageNumbers.append("&hellip;");
111
+ }
112
+
113
+ // Add the last page
114
+ pageNumbers.append(this._createPageLink(numPages, currentPage));
115
+ }
116
+ },
117
+
118
+ fetchPreviousPage: function(evt) {
119
+ this._fetchPage(evt, { direction: "prevPage" });
120
+ },
121
+
122
+ fetchNextPage: function(evt) {
123
+ this._fetchPage(evt, { direction: "nextPage" });
124
+ },
125
+
126
+ fetchPage: function(evt) {
127
+ var link = $(evt.currentTarget),
128
+ page = link.data("page");
129
+
130
+ this._fetchPage(evt, { page: page });
131
+ },
132
+
133
+ _createPageLink: function(pageNumber, currentPage) {
134
+ var attrs = { "href": "#", "data-page": pageNumber, "text": pageNumber };
135
+
136
+ if(pageNumber == currentPage) {
137
+ attrs["class"] = "disabled";
138
+ }
139
+ return $('<a/>', attrs);
140
+ },
141
+
142
+ _fetchPage: function(evt, options) {
143
+ evt.preventDefault();
144
+ var link = $(evt.currentTarget),
145
+ enabled = !link.hasClass("disabled");
146
+
147
+ if(enabled) {
148
+ var pageNum = options.page ? options.page : this.model[options.direction](),
149
+ fragment = ["page", pageNum].join("/"),
150
+ route = this._updateRoute(fragment);
151
+
152
+ Backbone.history.navigate(route, { trigger: true });
153
+ }
154
+ },
155
+
156
+ _updateRoute: function(fragment) {
157
+ var hash = location.hash;
158
+ if(hash.match(this.hashPartExpr)) {
159
+ return hash.replace(RegExp.$1, fragment);
160
+ } else {
161
+ return _([fragment, hash.replace("#", "")]).compact().join("/");
162
+ }
163
+ }
164
+ });
@@ -0,0 +1,6 @@
1
+ require "backbone_rails_extensions/version"
2
+
3
+
4
+ module BackboneRailsExtensions
5
+ class Engine < ::Rails::Engine; end
6
+ end
@@ -0,0 +1,3 @@
1
+ module BackboneRailsExtensions
2
+ VERSION = '0.9.3'
3
+ end
@@ -0,0 +1,48 @@
1
+ //= require spin
2
+ /*
3
+
4
+ You can now create a spinner using any of the variants below:
5
+
6
+ $("#el").spin(); // Produces default Spinner using the text color of #el.
7
+ $("#el").spin("small"); // Produces a 'small' Spinner using the text color of #el.
8
+ $("#el").spin("large", "white"); // Produces a 'large' Spinner in white (or any valid CSS color).
9
+ $("#el").spin({ ... }); // Produces a Spinner using your custom settings.
10
+
11
+ $("#el").spin(false); // Kills the spinner.
12
+
13
+ */
14
+ (function($) {
15
+ $.fn.spin = function(opts, color) {
16
+ var presets = {
17
+ "tiny": { lines: 8, length: 2, width: 2, radius: 3 },
18
+ "small": { lines: 8, length: 4, width: 3, radius: 5 },
19
+ "large": { lines: 10, length: 8, width: 4, radius: 8 }
20
+ };
21
+ if (Spinner) {
22
+ return this.each(function() {
23
+ var $this = $(this),
24
+ data = $this.data();
25
+
26
+ if (data.spinner) {
27
+ data.spinner.stop();
28
+ delete data.spinner;
29
+ }
30
+ if (opts !== false) {
31
+ if (typeof opts === "string") {
32
+ if (opts in presets) {
33
+ opts = presets[opts];
34
+ } else {
35
+ opts = {};
36
+ }
37
+ if (color) {
38
+ opts.color = color;
39
+ }
40
+ }
41
+ data.spinner = new Spinner($.extend({color: $this.css('color')}, opts)).spin(this);
42
+ }
43
+ });
44
+ } else {
45
+ throw "Spinner class not available.";
46
+ }
47
+ };
48
+ })(jQuery);
@@ -0,0 +1,295 @@
1
+ //fgnass.github.com/spin.js#v1.2.3
2
+ (function(window, document, undefined) {
3
+
4
+ /**
5
+ * Copyright (c) 2011 Felix Gnass [fgnass at neteye dot de]
6
+ * Licensed under the MIT license
7
+ */
8
+
9
+ var prefixes = ['webkit', 'Moz', 'ms', 'O'], /* Vendor prefixes */
10
+ animations = {}, /* Animation rules keyed by their name */
11
+ useCssAnimations;
12
+
13
+ /**
14
+ * Utility function to create elements. If no tag name is given,
15
+ * a DIV is created. Optionally properties can be passed.
16
+ */
17
+ function createEl(tag, prop) {
18
+ var el = document.createElement(tag || 'div'),
19
+ n;
20
+
21
+ for(n in prop) {
22
+ el[n] = prop[n];
23
+ }
24
+ return el;
25
+ }
26
+
27
+ /**
28
+ * Appends children and returns the parent.
29
+ */
30
+ function ins(parent /* child1, child2, ...*/) {
31
+ for(var i=1, n=arguments.length; i<n; i++) {
32
+ parent.appendChild(arguments[i]);
33
+ }
34
+ return parent;
35
+ }
36
+
37
+ /**
38
+ * Insert a new stylesheet to hold the @keyframe or VML rules.
39
+ */
40
+ var sheet = (function() {
41
+ var el = createEl('style');
42
+ ins(document.getElementsByTagName('head')[0], el);
43
+ return el.sheet || el.styleSheet;
44
+ })();
45
+
46
+ /**
47
+ * Creates an opacity keyframe animation rule and returns its name.
48
+ * Since most mobile Webkits have timing issues with animation-delay,
49
+ * we create separate rules for each line/segment.
50
+ */
51
+ function addAnimation(alpha, trail, i, lines) {
52
+ var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-'),
53
+ start = 0.01 + i/lines*100,
54
+ z = Math.max(1-(1-alpha)/trail*(100-start) , alpha),
55
+ prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase(),
56
+ pre = prefix && '-'+prefix+'-' || '';
57
+
58
+ if (!animations[name]) {
59
+ sheet.insertRule(
60
+ '@' + pre + 'keyframes ' + name + '{' +
61
+ '0%{opacity:'+z+'}' +
62
+ start + '%{opacity:'+ alpha + '}' +
63
+ (start+0.01) + '%{opacity:1}' +
64
+ (start+trail)%100 + '%{opacity:'+ alpha + '}' +
65
+ '100%{opacity:'+ z + '}' +
66
+ '}', 0);
67
+ animations[name] = 1;
68
+ }
69
+ return name;
70
+ }
71
+
72
+ /**
73
+ * Tries various vendor prefixes and returns the first supported property.
74
+ **/
75
+ function vendor(el, prop) {
76
+ var s = el.style,
77
+ pp,
78
+ i;
79
+
80
+ if(s[prop] !== undefined) return prop;
81
+ prop = prop.charAt(0).toUpperCase() + prop.slice(1);
82
+ for(i=0; i<prefixes.length; i++) {
83
+ pp = prefixes[i]+prop;
84
+ if(s[pp] !== undefined) return pp;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Sets multiple style properties at once.
90
+ */
91
+ function css(el, prop) {
92
+ for (var n in prop) {
93
+ el.style[vendor(el, n)||n] = prop[n];
94
+ }
95
+ return el;
96
+ }
97
+
98
+ /**
99
+ * Fills in default values.
100
+ */
101
+ function merge(obj) {
102
+ for (var i=1; i < arguments.length; i++) {
103
+ var def = arguments[i];
104
+ for (var n in def) {
105
+ if (obj[n] === undefined) obj[n] = def[n];
106
+ }
107
+ }
108
+ return obj;
109
+ }
110
+
111
+ /**
112
+ * Returns the absolute page-offset of the given element.
113
+ */
114
+ function pos(el) {
115
+ var o = {x:el.offsetLeft, y:el.offsetTop};
116
+ while((el = el.offsetParent)) {
117
+ o.x+=el.offsetLeft;
118
+ o.y+=el.offsetTop;
119
+ }
120
+ return o;
121
+ }
122
+
123
+ /** The constructor */
124
+ var Spinner = function Spinner(o) {
125
+ if (!this.spin) return new Spinner(o);
126
+ this.opts = merge(o || {}, Spinner.defaults, defaults);
127
+ },
128
+ defaults = Spinner.defaults = {
129
+ lines: 12, // The number of lines to draw
130
+ length: 7, // The length of each line
131
+ width: 5, // The line thickness
132
+ radius: 10, // The radius of the inner circle
133
+ color: '#000', // #rgb or #rrggbb
134
+ speed: 1, // Rounds per second
135
+ trail: 100, // Afterglow percentage
136
+ opacity: 1/4,
137
+ fps: 20
138
+ },
139
+ proto = Spinner.prototype = {
140
+ spin: function(target) {
141
+ this.stop();
142
+ var self = this,
143
+ el = self.el = css(createEl(), {position: 'relative'}),
144
+ ep, // element position
145
+ tp; // target position
146
+
147
+ if (target) {
148
+ target.insertBefore(el, target.firstChild||null);
149
+ tp = pos(target);
150
+ ep = pos(el);
151
+ css(el, {
152
+ left: (target.offsetWidth >> 1) - ep.x+tp.x + 'px',
153
+ top: (target.offsetHeight >> 1) - ep.y+tp.y + 'px'
154
+ });
155
+ }
156
+ el.setAttribute('aria-role', 'progressbar');
157
+ self.lines(el, self.opts);
158
+ if (!useCssAnimations) {
159
+ // No CSS animation support, use setTimeout() instead
160
+ var o = self.opts,
161
+ i = 0,
162
+ fps = o.fps,
163
+ f = fps/o.speed,
164
+ ostep = (1-o.opacity)/(f*o.trail / 100),
165
+ astep = f/o.lines;
166
+
167
+ (function anim() {
168
+ i++;
169
+ for (var s=o.lines; s; s--) {
170
+ var alpha = Math.max(1-(i+s*astep)%f * ostep, o.opacity);
171
+ self.opacity(el, o.lines-s, alpha, o);
172
+ }
173
+ self.timeout = self.el && setTimeout(anim, ~~(1000/fps));
174
+ })();
175
+ }
176
+ return self;
177
+ },
178
+ stop: function() {
179
+ var el = this.el;
180
+ if (el) {
181
+ clearTimeout(this.timeout);
182
+ if (el.parentNode) el.parentNode.removeChild(el);
183
+ this.el = undefined;
184
+ }
185
+ return this;
186
+ }
187
+ };
188
+ proto.lines = function(el, o) {
189
+ var i = 0,
190
+ seg;
191
+
192
+ function fill(color, shadow) {
193
+ return css(createEl(), {
194
+ position: 'absolute',
195
+ width: (o.length+o.width) + 'px',
196
+ height: o.width + 'px',
197
+ background: color,
198
+ boxShadow: shadow,
199
+ transformOrigin: 'left',
200
+ transform: 'rotate(' + ~~(360/o.lines*i) + 'deg) translate(' + o.radius+'px' +',0)',
201
+ borderRadius: (o.width>>1) + 'px'
202
+ });
203
+ }
204
+ for (; i < o.lines; i++) {
205
+ seg = css(createEl(), {
206
+ position: 'absolute',
207
+ top: 1+~(o.width/2) + 'px',
208
+ transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
209
+ opacity: o.opacity,
210
+ animation: useCssAnimations && addAnimation(o.opacity, o.trail, i, o.lines) + ' ' + 1/o.speed + 's linear infinite'
211
+ });
212
+ if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}));
213
+ ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)')));
214
+ }
215
+ return el;
216
+ };
217
+ proto.opacity = function(el, i, val) {
218
+ if (i < el.childNodes.length) el.childNodes[i].style.opacity = val;
219
+ };
220
+
221
+ /////////////////////////////////////////////////////////////////////////
222
+ // VML rendering for IE
223
+ /////////////////////////////////////////////////////////////////////////
224
+
225
+ /**
226
+ * Check and init VML support
227
+ */
228
+ (function() {
229
+ var s = css(createEl('group'), {behavior: 'url(#default#VML)'}),
230
+ i;
231
+
232
+ if (!vendor(s, 'transform') && s.adj) {
233
+
234
+ // VML support detected. Insert CSS rules ...
235
+ for (i=4; i--;) sheet.addRule(['group', 'roundrect', 'fill', 'stroke'][i], 'behavior:url(#default#VML)');
236
+
237
+ proto.lines = function(el, o) {
238
+ var r = o.length+o.width,
239
+ s = 2*r;
240
+
241
+ function grp() {
242
+ return css(createEl('group', {coordsize: s +' '+s, coordorigin: -r +' '+-r}), {width: s, height: s});
243
+ }
244
+
245
+ var g = grp(),
246
+ margin = ~(o.length+o.radius+o.width)+'px',
247
+ i;
248
+
249
+ function seg(i, dx, filter) {
250
+ ins(g,
251
+ ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
252
+ ins(css(createEl('roundrect', {arcsize: 1}), {
253
+ width: r,
254
+ height: o.width,
255
+ left: o.radius,
256
+ top: -o.width>>1,
257
+ filter: filter
258
+ }),
259
+ createEl('fill', {color: o.color, opacity: o.opacity}),
260
+ createEl('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
261
+ )
262
+ )
263
+ );
264
+ }
265
+
266
+ if (o.shadow) {
267
+ for (i = 1; i <= o.lines; i++) {
268
+ seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)');
269
+ }
270
+ }
271
+ for (i = 1; i <= o.lines; i++) {
272
+ seg(i);
273
+ }
274
+ return ins(css(el, {
275
+ margin: margin + ' 0 0 ' + margin,
276
+ zoom: 1
277
+ }), g);
278
+ };
279
+ proto.opacity = function(el, i, val, o) {
280
+ var c = el.firstChild;
281
+ o = o.shadow && o.lines || 0;
282
+ if (c && i+o < c.childNodes.length) {
283
+ c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild;
284
+ if (c) c.opacity = val;
285
+ }
286
+ };
287
+ }
288
+ else {
289
+ useCssAnimations = vendor(s, 'animation');
290
+ }
291
+ })();
292
+
293
+ window.Spinner = Spinner;
294
+
295
+ })(window, document);
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: backbone_rails_extensions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.3
5
+ platform: ruby
6
+ authors:
7
+ - Coroutine
8
+ - Tim Lowrimore
9
+ - John Dugan
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2015-07-22 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: This gem provides a set of Backbone.js extensions commonly used in Coroutine
16
+ projects. These extensions include simple collection views, paginated collection
17
+ views, searching, and loading indicators.
18
+ email:
19
+ - gems@coroutine.com
20
+ executables: []
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - ".gitignore"
25
+ - Gemfile
26
+ - LICENSE.txt
27
+ - README.md
28
+ - Rakefile
29
+ - backbone_rails_extensions.gemspec
30
+ - lib/assets/javascripts/jquery/serialize-object.js
31
+ - lib/assets/javascripts/list/namespace.js
32
+ - lib/assets/javascripts/list/routers/base.js
33
+ - lib/assets/javascripts/list/views/collection_view.js
34
+ - lib/assets/javascripts/list/views/search.js
35
+ - lib/assets/javascripts/list/views/simple_collection_view.js
36
+ - lib/assets/javascripts/loader/namespace.js
37
+ - lib/assets/javascripts/loader/sync.js
38
+ - lib/assets/javascripts/loader/utils/counting_latch.js
39
+ - lib/assets/javascripts/loader/views/base.js
40
+ - lib/assets/javascripts/pagination/collections/paginated_collection.js
41
+ - lib/assets/javascripts/pagination/models/pagination.js
42
+ - lib/assets/javascripts/pagination/namespace.js
43
+ - lib/assets/javascripts/pagination/templates/pagination.jst.ejs
44
+ - lib/assets/javascripts/pagination/views/pagination.js
45
+ - lib/backbone_rails_extensions.rb
46
+ - lib/backbone_rails_extensions/version.rb
47
+ - vendor/assets/javascripts/jquery.spin.js
48
+ - vendor/assets/javascripts/spin.js
49
+ homepage: https://github.com/coroutine/backbone-rails-extensions
50
+ licenses:
51
+ - MIT
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 2.4.8
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: This gem provides a set of Backbone.js extensions commonly used in Coroutine
73
+ projects.
74
+ test_files: []