grapple 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +182 -0
- data/Rakefile +9 -0
- data/app/assets/images/grapple/arrow-down.png +0 -0
- data/app/assets/images/grapple/arrow-up.png +0 -0
- data/app/assets/images/grapple/loading-bar.gif +0 -0
- data/app/assets/images/grapple/minus.png +0 -0
- data/app/assets/images/grapple/plus.png +0 -0
- data/app/assets/javascripts/grapple-history.js +81 -0
- data/app/assets/javascripts/grapple-jquery.js +202 -0
- data/app/assets/javascripts/grapple.js +39 -0
- data/app/assets/stylesheets/grapple.css +252 -0
- data/config/locales/en.yml +2 -0
- data/lib/grapple/ajax_data_grid_builder.rb +38 -0
- data/lib/grapple/base_table_builder.rb +58 -0
- data/lib/grapple/components/actions.rb +24 -0
- data/lib/grapple/components/base_component.rb +91 -0
- data/lib/grapple/components/column_headings.rb +42 -0
- data/lib/grapple/components/html_body.rb +37 -0
- data/lib/grapple/components/html_colgroup.rb +14 -0
- data/lib/grapple/components/html_component.rb +24 -0
- data/lib/grapple/components/html_footer.rb +14 -0
- data/lib/grapple/components/html_header.rb +16 -0
- data/lib/grapple/components/html_row.rb +11 -0
- data/lib/grapple/components/search_form.rb +27 -0
- data/lib/grapple/components/search_query_field.rb +14 -0
- data/lib/grapple/components/search_submit.rb +11 -0
- data/lib/grapple/components/toolbar.rb +15 -0
- data/lib/grapple/components/will_paginate_infobar.rb +22 -0
- data/lib/grapple/components/will_paginate_pagination.rb +30 -0
- data/lib/grapple/components.rb +23 -0
- data/lib/grapple/data_grid_builder.rb +24 -0
- data/lib/grapple/engine.rb +11 -0
- data/lib/grapple/helpers/table_helper.rb +31 -0
- data/lib/grapple/helpers.rb +8 -0
- data/lib/grapple/html_table_builder.rb +31 -0
- data/lib/grapple.rb +14 -0
- data/spec/builders/ajax_data_grid_builder_spec.rb +19 -0
- data/spec/components/actions_spec.rb +33 -0
- data/spec/components/column_headings_spec.rb +33 -0
- data/spec/components/html_body_spec.rb +53 -0
- data/spec/components/html_colgroup_spec.rb +38 -0
- data/spec/components/html_footer_spec.rb +38 -0
- data/spec/components/search_form_spec.rb +29 -0
- data/spec/components/toolbar_spec.rb +38 -0
- data/spec/components/will_paginate_spec.rb +33 -0
- data/spec/fixtures/schema.rb +17 -0
- data/spec/fixtures/users.yml +56 -0
- data/spec/spec_helper.rb +137 -0
- data/spec/support/test_environment.rb +30 -0
- metadata +207 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cbdfba833204a62c126fb91ec6b95e6b17a76654
|
4
|
+
data.tar.gz: ee880107cf1becc73adc3c46d0f908be8b7ff465
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1976ae3090721348125616fdb2703bf5e568849c366c62904a81de9b6359a072522cc2fa8307d13e983961f1e9546ad54dac01ac6fc51e29bed25486eb262865
|
7
|
+
data.tar.gz: 591e70f06fbf5ef355a692fc082a7016a77d993a021b3f6f89c9aaff91c2eb22303825fe4f6d8496e372fffca403116aa3176a7b41938fb444363ecdabcc174e
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Equal Level
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
# grapple
|
2
|
+
Customizable data grid for Rails
|
3
|
+
|
4
|
+
## Features
|
5
|
+
* Modular design
|
6
|
+
* Server side rendering
|
7
|
+
* Usable out of the box
|
8
|
+
* Sorting
|
9
|
+
* Searching/Filtering
|
10
|
+
* Pagination
|
11
|
+
* AJAX
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
# Gemfile for Rails 3+
|
17
|
+
gem 'grapple'
|
18
|
+
```
|
19
|
+
|
20
|
+
``` css
|
21
|
+
/* app/assets/stylesheets/application.css */
|
22
|
+
*= require grapple
|
23
|
+
```
|
24
|
+
|
25
|
+
## Dependencies
|
26
|
+
* Rails 3+
|
27
|
+
|
28
|
+
Optional Dependencies:
|
29
|
+
|
30
|
+
* will_paginate - for pagination support
|
31
|
+
* jQuery - for AJAX support
|
32
|
+
* history.js - for back button support when using the AJAX data table
|
33
|
+
|
34
|
+
## Table Builders
|
35
|
+
HtmlTableBuilder - A basic HTML table builder
|
36
|
+
|
37
|
+
DataGridBuilder (default) - An HTML table builder with support for paging, filtering, sorting, and actions.
|
38
|
+
|
39
|
+
AjaxDataGridBuilder - DataGridBuilder that uses AJAX to retrieve results when sorting/filtering the table.
|
40
|
+
|
41
|
+
In an initializer set the default builder:
|
42
|
+
``` ruby
|
43
|
+
Grapple::Helpers::TableHelper.builder = Grapple::AjaxDataGridBuilder
|
44
|
+
```
|
45
|
+
|
46
|
+
## Basic Usage (DataGridBuilder)
|
47
|
+
app/controllers/posts_controller.rb
|
48
|
+
``` ruby
|
49
|
+
class PostsController < ApplicationController
|
50
|
+
def index
|
51
|
+
@posts = Post.all
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
app/views/posts/index.html.erb
|
57
|
+
``` HTML+ERB
|
58
|
+
<%
|
59
|
+
columns = [
|
60
|
+
{ label: 'Name' },
|
61
|
+
{ label: 'Title' },
|
62
|
+
{ label: 'Content' },
|
63
|
+
{ label: '' },
|
64
|
+
{ label: '' },
|
65
|
+
{ label: '' }
|
66
|
+
]
|
67
|
+
|
68
|
+
actions = [
|
69
|
+
{ label: 'New Post', url: new_posts_path }
|
70
|
+
]
|
71
|
+
%>
|
72
|
+
<%= table_for(columns, @posts) do |t| %>
|
73
|
+
<%= t.header do %>
|
74
|
+
<%= t.toolbar do %>
|
75
|
+
<%= t.actions actions %>
|
76
|
+
<% end %>
|
77
|
+
<%= t.column_headings %>
|
78
|
+
<% end %>
|
79
|
+
<%= t.body do |item| %>
|
80
|
+
<td><%= post.name %></td>
|
81
|
+
<td><%= post.title %></td>
|
82
|
+
<td><%= post.content %></td>
|
83
|
+
<td><%= link_to 'Show', post %></td>
|
84
|
+
<td><%= link_to 'Edit', edit_post_path(post) %></td>
|
85
|
+
<td><%= link_to 'Destroy', post, confirm: 'Are you sure?', method: :delete %></td>
|
86
|
+
<% end %>
|
87
|
+
<% end %>
|
88
|
+
```
|
89
|
+
|
90
|
+
## Sorting
|
91
|
+
TODO
|
92
|
+
|
93
|
+
## Pagination (requires will_paginate)
|
94
|
+
app/controllers/posts_controller.rb
|
95
|
+
``` ruby
|
96
|
+
def index
|
97
|
+
@posts = Post.paginate(page: params[:page] || 1, per_page: 10)
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
app/views/posts/index.html.erb
|
102
|
+
``` HTML+ERB
|
103
|
+
<%= table_for(columns, @posts) do |t| %>
|
104
|
+
<%= t.header %>
|
105
|
+
<%= t.footer do %>
|
106
|
+
<%= t.pagination %>
|
107
|
+
<% end %>
|
108
|
+
<% end %>
|
109
|
+
```
|
110
|
+
|
111
|
+
## Filtering/Searching
|
112
|
+
TODO
|
113
|
+
|
114
|
+
## Actions
|
115
|
+
The Actions component can be used to generate buttons/links for actions related to the table. This can be used to provide links to export the data in the table or create new objects.
|
116
|
+
``` HTML+ERB
|
117
|
+
<%= table_for(columns, @posts) do |t| %>
|
118
|
+
<%= t.header do %>
|
119
|
+
<%= t.toolbar do %>
|
120
|
+
<%= t.actions [
|
121
|
+
{ label: :new_post, url: new_posts_path },
|
122
|
+
{ label: :export_posts, url: export_posts_path }
|
123
|
+
] %>
|
124
|
+
<% end %>
|
125
|
+
<%= t.column_headings %>
|
126
|
+
<% end %>
|
127
|
+
<% end %>
|
128
|
+
```
|
129
|
+
|
130
|
+
## AJAX
|
131
|
+
The AjaxDataGridBuilder generates tables that can update their content using AJAX rather than re-loading the page. jQuery is required.
|
132
|
+
``` javascript
|
133
|
+
// app/assets/javascripts/application.js
|
134
|
+
//= require grapple
|
135
|
+
//= require grapple-jquery
|
136
|
+
```
|
137
|
+
|
138
|
+
``` ruby
|
139
|
+
# app/controllers/posts_controller.rb
|
140
|
+
class PostsController < ApplicationController
|
141
|
+
def index
|
142
|
+
@posts = table_results
|
143
|
+
end
|
144
|
+
|
145
|
+
# Method called by AJAX requests - renders the table without a layout
|
146
|
+
def table
|
147
|
+
@posts = table_results
|
148
|
+
render partial: 'table'
|
149
|
+
end
|
150
|
+
|
151
|
+
protected
|
152
|
+
|
153
|
+
def table_results
|
154
|
+
Post.paginate(page: params[:page] || 1, per_page: 10)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
Create a container around the table that can be updated by the JavaScript
|
160
|
+
``` HTML+ERB
|
161
|
+
<%# app/views/posts/index.html.erb %>
|
162
|
+
<%= grapple_container(id: 'posts_table') do %>
|
163
|
+
<%= render :partial => 'table' %>
|
164
|
+
<% end %>
|
165
|
+
```
|
166
|
+
|
167
|
+
Render the table using `table_for` in `app/views/posts/_table.html.erb`
|
168
|
+
|
169
|
+
## History w/AJAX (back button)
|
170
|
+
|
171
|
+
Requires: https://github.com/browserstate/history.js/
|
172
|
+
|
173
|
+
``` javascript
|
174
|
+
// app/assets/javascripts/application.js
|
175
|
+
//= require jquery-history
|
176
|
+
//= require grapple
|
177
|
+
//= require grapple-history
|
178
|
+
//= require grapple-jquery
|
179
|
+
```
|
180
|
+
|
181
|
+
## Customizing
|
182
|
+
TODO
|
data/Rakefile
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,81 @@
|
|
1
|
+
(function(window, Grapple, $) {
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
var urlQuery = Grapple.Util.urlQuery,
|
5
|
+
parseUrlQuery = Grapple.Util.parseUrlQuery;
|
6
|
+
|
7
|
+
var GrappleHistory = function() {
|
8
|
+
if(History.init) {
|
9
|
+
// https://github.com/browserstate/history.js/
|
10
|
+
this.api = History;
|
11
|
+
|
12
|
+
// Initialization of history.js can be delayed
|
13
|
+
// if it was do it now
|
14
|
+
if(this.api.options && this.api.options.delayInit) {
|
15
|
+
this.api.options.delayInit = false;
|
16
|
+
this.api.init();
|
17
|
+
}
|
18
|
+
}
|
19
|
+
else {
|
20
|
+
// TODO: support native history api
|
21
|
+
this.api = window.history;
|
22
|
+
}
|
23
|
+
this.api = History;
|
24
|
+
this.changeCallback = null;
|
25
|
+
};
|
26
|
+
|
27
|
+
// Don't clutter the url with rails form parameters
|
28
|
+
GrappleHistory.IGNORE_PARAMS = { 'utf8': true, 'authenticity_token': true };
|
29
|
+
|
30
|
+
GrappleHistory.prototype = {
|
31
|
+
|
32
|
+
add: function(namespace, params) {
|
33
|
+
var state = this.api.getState();
|
34
|
+
var historyParams = parseUrlQuery(urlQuery(state.url));
|
35
|
+
var newParams = parseUrlQuery(params);
|
36
|
+
|
37
|
+
// Remove any parameters from the current state
|
38
|
+
// that are for this table
|
39
|
+
for(var x in historyParams) {
|
40
|
+
var remove = namespace ?
|
41
|
+
// Remove any parameters in the tables namespace
|
42
|
+
x.indexOf(namespace + '.') === 0 :
|
43
|
+
// Table is in the global namespace, remove any parameters that aren't namespaced
|
44
|
+
x.indexOf('.') === -1;
|
45
|
+
|
46
|
+
if(remove) {
|
47
|
+
delete historyParams[x];
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
// Add the new parameters
|
52
|
+
for(var x in newParams) {
|
53
|
+
if(GrappleHistory.IGNORE_PARAMS[x]) continue;
|
54
|
+
var key = namespace ? namespace + '.' + x : x;
|
55
|
+
historyParams[key] = newParams[key];
|
56
|
+
}
|
57
|
+
|
58
|
+
this.api.pushState(null, document.title, '?' + $.param(historyParams));
|
59
|
+
},
|
60
|
+
|
61
|
+
subscribe: function(callback) {
|
62
|
+
var api = this.api;
|
63
|
+
this.changeCallback = function(event) {
|
64
|
+
var state = api.getState();
|
65
|
+
callback(parseUrlQuery(urlQuery(state.url)));
|
66
|
+
};
|
67
|
+
$(window).bind('statechange', this.changeCallback);
|
68
|
+
},
|
69
|
+
|
70
|
+
unsubscribe: function() {
|
71
|
+
if(this.changeCallback) {
|
72
|
+
$(window).unbind('statechange', this.changeCallback);
|
73
|
+
this.changeCallback = null;
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
};
|
78
|
+
|
79
|
+
Grapple.History = GrappleHistory;
|
80
|
+
|
81
|
+
})(window, Grapple, $);
|
@@ -0,0 +1,202 @@
|
|
1
|
+
(function(Grapple, $) {
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
var urlQuery = Grapple.Util.urlQuery,
|
5
|
+
parseUrlQuery = Grapple.Util.parseUrlQuery;
|
6
|
+
|
7
|
+
var overrideLink = function(clickable, anchor, callback) {
|
8
|
+
var href = $(anchor).attr('href');
|
9
|
+
$(anchor).attr('href', 'javascript:void(0)');
|
10
|
+
$(clickable).on('click', function() {
|
11
|
+
callback(href ? href.split('?')[1] : '');
|
12
|
+
});
|
13
|
+
};
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Creates a new instance of the Grapple AJAX widget.
|
17
|
+
*
|
18
|
+
* @param {String} Selector for the table container element.
|
19
|
+
* @param {Object} Hash of options for the table (url, history)
|
20
|
+
*/
|
21
|
+
var GrappleTable = function(element, options) {
|
22
|
+
options = options || {};
|
23
|
+
this.element = $(element);
|
24
|
+
this.url = options.url || this.element.data('grapple-ajax-url');
|
25
|
+
this.namespace = options.namespace || this.element.data('grapple-ajax-namespace') || null;
|
26
|
+
this.currentParams = options.params || '';
|
27
|
+
if(typeof options.history !== 'undefined' && options.history !== true) {
|
28
|
+
this.history = options.history;
|
29
|
+
}
|
30
|
+
else if(this.element.data('grapple-ajax-history') == 1 || options.history === true) {
|
31
|
+
this.history = new Grapple.History();
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
this.history = null;
|
35
|
+
}
|
36
|
+
this.init();
|
37
|
+
};
|
38
|
+
|
39
|
+
GrappleTable.CSS_AJAX_LOADING = 'grapple-ajax-loading';
|
40
|
+
GrappleTable.CSS_LOADING = 'grapple-loading';
|
41
|
+
GrappleTable.CSS_LOADING_OVERLAY = 'loading-overlay';
|
42
|
+
GrappleTable.NON_TABLE_RESPONSE = '<!DOCTYPE html>';
|
43
|
+
|
44
|
+
GrappleTable.prototype = {
|
45
|
+
|
46
|
+
/**
|
47
|
+
*
|
48
|
+
*/
|
49
|
+
init: function() {
|
50
|
+
var self = this;
|
51
|
+
self.table = self.element.children('table');
|
52
|
+
self.header = self.table.children('thead');
|
53
|
+
self.body = self.table.children('tbody');
|
54
|
+
self.footer = self.table.children('tfoot');
|
55
|
+
|
56
|
+
self.initSorting();
|
57
|
+
self.initSearchForm();
|
58
|
+
self.initPagination();
|
59
|
+
self.initHistory();
|
60
|
+
|
61
|
+
self.element.removeClass(GrappleTable.CSS_AJAX_LOADING);
|
62
|
+
},
|
63
|
+
|
64
|
+
initHistory: function() {
|
65
|
+
if(this.history) {
|
66
|
+
var self = this;
|
67
|
+
this.history.unsubscribe();
|
68
|
+
this.history = new Grapple.History();
|
69
|
+
this.history.subscribe(function(params) {
|
70
|
+
self.onHistoryChange(params);
|
71
|
+
});
|
72
|
+
}
|
73
|
+
},
|
74
|
+
|
75
|
+
onHistoryChange: function(params) {
|
76
|
+
console.log("HISTORY CHANGE");
|
77
|
+
this._showLoading();
|
78
|
+
this._updateTable($.param(params));
|
79
|
+
},
|
80
|
+
|
81
|
+
/**
|
82
|
+
*
|
83
|
+
*/
|
84
|
+
loadTable: function(params) {
|
85
|
+
console.log("LOAD TABLE ", params);
|
86
|
+
this._showLoading();
|
87
|
+
|
88
|
+
if(this.history) {
|
89
|
+
console.log("ADD History");
|
90
|
+
this.history.unsubscribe();
|
91
|
+
this.history.add(this.namespace, params);
|
92
|
+
}
|
93
|
+
|
94
|
+
console.log("Update table");
|
95
|
+
this._updateTable(params);
|
96
|
+
},
|
97
|
+
|
98
|
+
_showLoading: function() {
|
99
|
+
// Add loading class to the container
|
100
|
+
this.element.addClass(GrappleTable.CSS_LOADING);
|
101
|
+
|
102
|
+
// Set the position of the loading overlay based on the size of the table
|
103
|
+
var loadingBar = this.element.find('.' + GrappleTable.CSS_LOADING_OVERLAY)
|
104
|
+
loadingBar.width(this.table.width());
|
105
|
+
var barHeight = loadingBar.height() || 20;
|
106
|
+
var top = (this.table.height() / 2) - barHeight;
|
107
|
+
loadingBar.css('top', top + 'px');
|
108
|
+
},
|
109
|
+
|
110
|
+
_hideLoading: function() {
|
111
|
+
// Remove the loading class from the container
|
112
|
+
this.element.removeClass(GrappleTable.CSS_LOADING);
|
113
|
+
},
|
114
|
+
|
115
|
+
_updateTable: function(params) {
|
116
|
+
var self = this;
|
117
|
+
var url = this.url;
|
118
|
+
if(params.length) {
|
119
|
+
url += '?' + params;
|
120
|
+
}
|
121
|
+
|
122
|
+
$.ajax(url, {
|
123
|
+
success: function(data) {
|
124
|
+
// HACK
|
125
|
+
var nonTableKeyIndex = data.indexOf(GrappleTable.NON_TABLE_RESPONSE);
|
126
|
+
if(nonTableKeyIndex > -1 && nonTableKeyIndex < 100) {
|
127
|
+
data = "Failed to load table";
|
128
|
+
}
|
129
|
+
self.element.addClass(GrappleTable.CSS_AJAX_LOADING);
|
130
|
+
self.element.html(data);
|
131
|
+
self.init();
|
132
|
+
self._hideLoading();
|
133
|
+
},
|
134
|
+
error: function(a, b, c) {
|
135
|
+
// TODO: handle loading errors
|
136
|
+
console.log("Failed to load table");
|
137
|
+
console.log(a);
|
138
|
+
console.log(b);
|
139
|
+
console.log(c);
|
140
|
+
}
|
141
|
+
});
|
142
|
+
},
|
143
|
+
|
144
|
+
initSorting: function() {
|
145
|
+
var self = this;
|
146
|
+
this.header.find('th.sortable').each(function(i, elem) {
|
147
|
+
overrideLink(elem, $(elem).find('a'), function(params) {
|
148
|
+
// Return to the first page on sorting
|
149
|
+
params = params.replace(/&?page=[0-9]+/, '');
|
150
|
+
params += '&page=1';
|
151
|
+
self.loadTable(params);
|
152
|
+
});
|
153
|
+
});
|
154
|
+
},
|
155
|
+
|
156
|
+
initSearchForm: function() {
|
157
|
+
var self = this;
|
158
|
+
this.header.find('form.search-form').each(function(i, elem) {
|
159
|
+
$(elem).on('submit', function(event) {
|
160
|
+
// Don't submit the form
|
161
|
+
event.preventDefault();
|
162
|
+
self.loadTable($(elem).serialize());
|
163
|
+
});
|
164
|
+
});
|
165
|
+
},
|
166
|
+
|
167
|
+
initPagination: function() {
|
168
|
+
var self = this;
|
169
|
+
this.footer.find('.pagination a').each(function(i, elem) {
|
170
|
+
overrideLink(elem, elem, function(params) {
|
171
|
+
self.loadTable(params);
|
172
|
+
});
|
173
|
+
});
|
174
|
+
}
|
175
|
+
|
176
|
+
};
|
177
|
+
|
178
|
+
Grapple.Table = GrappleTable;
|
179
|
+
|
180
|
+
function Plugin(option) {
|
181
|
+
return this.each(function() {
|
182
|
+
var $this = $(this);
|
183
|
+
var data = $this.data('grapple');
|
184
|
+
var options = typeof option == 'object' && option;
|
185
|
+
|
186
|
+
if (!data && /destroy|hide/.test(option)) return;
|
187
|
+
if (!data) $this.data('grapple', (data = new GrappleTable(this, options)));
|
188
|
+
if (typeof option == 'string') data[option]();
|
189
|
+
});
|
190
|
+
}
|
191
|
+
|
192
|
+
var old = $.fn.grapple;
|
193
|
+
|
194
|
+
$.fn.grapple = Plugin;
|
195
|
+
$.fn.grapple.Constructor = GrappleTable;
|
196
|
+
|
197
|
+
$.fn.grapple.noConflict = function() {
|
198
|
+
$.fn.grapple = old;
|
199
|
+
return this;
|
200
|
+
}
|
201
|
+
|
202
|
+
})(Grapple, jQuery);
|
@@ -0,0 +1,39 @@
|
|
1
|
+
(function(globals) {
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
// Namespace
|
5
|
+
var Grapple = {};
|
6
|
+
|
7
|
+
var decodeParam = function(str) {
|
8
|
+
return decodeURIComponent(str.replace(/\+/g, " "));
|
9
|
+
};
|
10
|
+
|
11
|
+
var parseUrlQuery = function(query) {
|
12
|
+
var regex = /([^&=]+)=?([^&]*)/g;
|
13
|
+
var params = {}, e;
|
14
|
+
while(e = regex.exec(query)) {
|
15
|
+
var k = decodeParam(e[1]), v = decodeParam(e[2]);
|
16
|
+
if(k.substring(k.length - 2) === '[]') {
|
17
|
+
k = k.substring(0, k.length - 2);
|
18
|
+
(params[k] || (params[k] = [])).push(v);
|
19
|
+
}
|
20
|
+
else {
|
21
|
+
params[k] = v;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
return params;
|
25
|
+
};
|
26
|
+
|
27
|
+
// Get the query string from a url, returns an empty string if there is no query
|
28
|
+
var urlQuery = function(url) {
|
29
|
+
return url.split('?')[1] || '';
|
30
|
+
};
|
31
|
+
|
32
|
+
globals.Grapple = {
|
33
|
+
Util: {
|
34
|
+
urlQuery: urlQuery,
|
35
|
+
parseUrlQuery: parseUrlQuery
|
36
|
+
}
|
37
|
+
};
|
38
|
+
|
39
|
+
})(window);
|