browse-everything 0.4.5 → 0.5.0
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.
- checksums.yaml +8 -8
- data/HISTORY.md +6 -0
- data/README.md +3 -1
- data/app/assets/javascripts/browse_everything.js +2 -1
- data/app/assets/javascripts/browse_everything/behavior.js.coffee +65 -5
- data/app/assets/stylesheets/browse_everything.css.scss +28 -4
- data/app/assets/stylesheets/jquery.treetable.theme.browse.css +91 -0
- data/app/controllers/browse_everything_controller.rb +2 -2
- data/app/helpers/browse_everything_helper.rb +6 -0
- data/app/views/browse_everything/_file.html.erb +18 -0
- data/app/views/browse_everything/_files.html.erb +14 -8
- data/app/views/browse_everything/index.html.erb +1 -0
- data/app/views/layouts/browse_everything.html.erb +1 -1
- data/browse-everything.gemspec +2 -1
- data/config/routes.rb +1 -1
- data/lib/browse_everything/browser.rb +2 -2
- data/lib/browse_everything/driver/base.rb +4 -4
- data/lib/browse_everything/driver/drop_box.rb +2 -2
- data/lib/browse_everything/engine.rb +3 -1
- data/lib/browse_everything/file_entry.rb +5 -1
- data/lib/browse_everything/version.rb +1 -1
- data/spec/spec_helper.rb +5 -2
- data/spec/unit/browser_spec.rb +2 -2
- data/spec/unit/drop_box_spec.rb +1 -1
- data/spec/unit/file_entry_spec.rb +78 -30
- data/vendor/assets/javascripts/jquery.treetable.js +629 -0
- data/vendor/assets/stylesheets/jquery.treetable.css +28 -0
- data/vendor/assets/stylesheets/screen.css +28 -0
- metadata +23 -4
@@ -62,7 +62,7 @@ module BrowseEverything
|
|
62
62
|
private
|
63
63
|
def auth_flow
|
64
64
|
@csrf ||= {}
|
65
|
-
DropboxOAuth2Flow.new(config[:app_key], config[:app_secret], connector_response_url(config[:url_options]).to_s,@csrf
|
65
|
+
DropboxOAuth2Flow.new(config[:app_key], config[:app_secret], connector_response_url(config[:url_options]).to_s,@csrf,'token')
|
66
66
|
end
|
67
67
|
|
68
68
|
def client
|
@@ -71,4 +71,4 @@ module BrowseEverything
|
|
71
71
|
end
|
72
72
|
|
73
73
|
end
|
74
|
-
end
|
74
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
module BrowseEverything
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
initializer :assets do |config|
|
4
|
+
config.assets.paths << config.root.join('vendor', 'assets', 'javascripts')
|
5
|
+
config.assets.paths << config.root.join('vendor', 'assets', 'stylesheets')
|
4
6
|
Rails.application.config.assets.precompile += %w{ browse_everything.js }
|
5
7
|
Rails.application.config.assets.precompile += %w{ browse_everything.css }
|
6
8
|
end
|
7
9
|
end
|
8
|
-
end
|
10
|
+
end
|
@@ -9,9 +9,13 @@ module BrowseEverything
|
|
9
9
|
@size = size
|
10
10
|
@mtime = mtime
|
11
11
|
@container = container
|
12
|
-
@type = type || @container ? 'directory' : Rack::Mime.mime_type(File.extname(name))
|
12
|
+
@type = type || @container ? 'application/x-directory' : Rack::Mime.mime_type(File.extname(name))
|
13
13
|
end
|
14
14
|
|
15
|
+
def relative_parent_path?
|
16
|
+
name =~ /^\.\.?$/ ? true : false
|
17
|
+
end
|
18
|
+
|
15
19
|
def container?
|
16
20
|
@container
|
17
21
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require File.expand_path("config/environment", ENV['RAILS_ROOT'] || File.expand_path("../internal", __FILE__))
|
2
2
|
require 'rspec'
|
3
|
+
require 'rspec/its'
|
3
4
|
require 'webmock/rspec'
|
4
5
|
require 'simplecov'
|
5
6
|
require 'vcr'
|
@@ -14,8 +15,10 @@ VCR.configure do |c|
|
|
14
15
|
c.configure_rspec_metadata!
|
15
16
|
end
|
16
17
|
|
17
|
-
RSpec.configure do |
|
18
|
-
|
18
|
+
RSpec.configure do |config|
|
19
|
+
config.expect_with :rspec do |c|
|
20
|
+
c.syntax = [:should, :expect]
|
21
|
+
end
|
19
22
|
end
|
20
23
|
|
21
24
|
module BrowserConfigHelper
|
data/spec/unit/browser_spec.rb
CHANGED
@@ -26,7 +26,7 @@ describe BrowseEverything::Browser do
|
|
26
26
|
}
|
27
27
|
|
28
28
|
describe "file config" do
|
29
|
-
before(:each) { File.
|
29
|
+
before(:each) { allow(File).to receive(:read).and_return(file_config) }
|
30
30
|
subject { BrowseEverything::Browser.new(url_options) }
|
31
31
|
|
32
32
|
it "should have 2 providers" do
|
@@ -69,7 +69,7 @@ describe BrowseEverything::Browser do
|
|
69
69
|
}
|
70
70
|
|
71
71
|
it "should complain but continue" do
|
72
|
-
Rails.logger.
|
72
|
+
allow(Rails.logger).to receive(:warn).with('Unknown provider: foo')
|
73
73
|
expect(subject.providers.keys).to eq([:file_system,:drop_box])
|
74
74
|
end
|
75
75
|
end
|
data/spec/unit/drop_box_spec.rb
CHANGED
@@ -12,7 +12,7 @@ describe BrowseEverything::Driver::DropBox, vcr: { cassette_name: 'dropbox', rec
|
|
12
12
|
'code' => 'FakeDropboxAuthorizationCodeABCDEFG',
|
13
13
|
'state' => 'GjDcUhPNZrZzdsw%2FghBy2A%3D%3D|drop_box'
|
14
14
|
} }
|
15
|
-
let(:csrf_data) { {token
|
15
|
+
let(:csrf_data) { {'token' => 'GjDcUhPNZrZzdsw%2FghBy2A%3D%3D'} }
|
16
16
|
|
17
17
|
it "#validate_config" do
|
18
18
|
expect { BrowseEverything::Driver::DropBox.new({}) }.to raise_error(BrowseEverything::InitializationError)
|
@@ -1,44 +1,92 @@
|
|
1
1
|
require File.expand_path('../../spec_helper',__FILE__)
|
2
2
|
|
3
3
|
describe BrowseEverything::FileEntry do
|
4
|
+
|
4
5
|
let(:mtime) { Time.now }
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
it "should be a BrowseEverything::FileEntry" do
|
13
|
-
expect(subject).to be_a BrowseEverything::FileEntry
|
14
|
-
end
|
6
|
+
describe "regular file" do
|
7
|
+
subject {
|
8
|
+
BrowseEverything::FileEntry.new(
|
9
|
+
'file_id_01234', 'my_provider:/location/pa/th/file.m4v',
|
10
|
+
'file.m4v', '1.2 GB', mtime, false
|
11
|
+
)
|
12
|
+
}
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
it "should be a BrowseEverything::FileEntry" do
|
15
|
+
expect(subject).to be_a BrowseEverything::FileEntry
|
16
|
+
end
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
it "#id" do
|
19
|
+
expect(subject.id).to eq("file_id_01234")
|
20
|
+
end
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
it "#location" do
|
23
|
+
expect(subject.location).to eq("my_provider:/location/pa/th/file.m4v")
|
24
|
+
end
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
it "#name" do
|
27
|
+
expect(subject.name).to eq("file.m4v")
|
28
|
+
end
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
it "#size" do
|
31
|
+
expect(subject.size).to eq("1.2 GB")
|
32
|
+
end
|
35
33
|
|
36
|
-
|
37
|
-
|
34
|
+
it "#mtime" do
|
35
|
+
expect(subject.mtime).to eq(mtime)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "#type" do
|
39
|
+
expect(subject.type).to eq("video/mp4")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "#container?" do
|
43
|
+
expect(subject.container?).to be false
|
44
|
+
end
|
45
|
+
|
46
|
+
it "#relative_parent_path?" do
|
47
|
+
expect(subject.relative_parent_path?).to be false
|
48
|
+
end
|
38
49
|
end
|
50
|
+
|
51
|
+
describe "directory" do
|
52
|
+
subject {
|
53
|
+
BrowseEverything::FileEntry.new(
|
54
|
+
'directory_id_1234', 'my_provider:/location/pa/th',
|
55
|
+
'th', '', mtime, true
|
56
|
+
)
|
57
|
+
}
|
58
|
+
|
59
|
+
it "#type" do
|
60
|
+
expect(subject.type).to eq("application/x-directory")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "#container?" do
|
64
|
+
expect(subject.container?).to be true
|
65
|
+
end
|
39
66
|
|
40
|
-
|
41
|
-
|
67
|
+
it "#relative_parent_path?" do
|
68
|
+
expect(subject.relative_parent_path?).to be false
|
69
|
+
end
|
42
70
|
end
|
71
|
+
|
72
|
+
describe "parent path" do
|
73
|
+
subject {
|
74
|
+
BrowseEverything::FileEntry.new(
|
75
|
+
'directory_id_1234', 'my_provider:/location/pa/th',
|
76
|
+
'..', '', mtime, true
|
77
|
+
)
|
78
|
+
}
|
43
79
|
|
44
|
-
|
80
|
+
it "#type" do
|
81
|
+
expect(subject.type).to eq("application/x-directory")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "#container?" do
|
85
|
+
expect(subject.container?).to be true
|
86
|
+
end
|
87
|
+
|
88
|
+
it "#relative_parent_path?" do
|
89
|
+
expect(subject.relative_parent_path?).to be true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,629 @@
|
|
1
|
+
/*
|
2
|
+
* jQuery treetable Plugin 3.2.0
|
3
|
+
* http://ludo.cubicphuse.nl/jquery-treetable
|
4
|
+
*
|
5
|
+
* Copyright 2013, Ludo van den Boom
|
6
|
+
* Dual licensed under the MIT or GPL Version 2 licenses.
|
7
|
+
*/
|
8
|
+
(function($) {
|
9
|
+
var Node, Tree, methods;
|
10
|
+
|
11
|
+
Node = (function() {
|
12
|
+
function Node(row, tree, settings) {
|
13
|
+
var parentId;
|
14
|
+
|
15
|
+
this.row = row;
|
16
|
+
this.tree = tree;
|
17
|
+
this.settings = settings;
|
18
|
+
|
19
|
+
// TODO Ensure id/parentId is always a string (not int)
|
20
|
+
this.id = this.row.data(this.settings.nodeIdAttr);
|
21
|
+
|
22
|
+
// TODO Move this to a setParentId function?
|
23
|
+
parentId = this.row.data(this.settings.parentIdAttr);
|
24
|
+
if (parentId != null && parentId !== "") {
|
25
|
+
this.parentId = parentId;
|
26
|
+
}
|
27
|
+
|
28
|
+
this.treeCell = $(this.row.children(this.settings.columnElType)[this.settings.column]);
|
29
|
+
this.expander = $(this.settings.expanderTemplate);
|
30
|
+
this.indenter = $(this.settings.indenterTemplate);
|
31
|
+
this.children = [];
|
32
|
+
this.initialized = false;
|
33
|
+
this.treeCell.prepend(this.indenter);
|
34
|
+
}
|
35
|
+
|
36
|
+
Node.prototype.addChild = function(child) {
|
37
|
+
return this.children.push(child);
|
38
|
+
};
|
39
|
+
|
40
|
+
Node.prototype.ancestors = function() {
|
41
|
+
var ancestors, node;
|
42
|
+
node = this;
|
43
|
+
ancestors = [];
|
44
|
+
while (node = node.parentNode()) {
|
45
|
+
ancestors.push(node);
|
46
|
+
}
|
47
|
+
return ancestors;
|
48
|
+
};
|
49
|
+
|
50
|
+
Node.prototype.collapse = function() {
|
51
|
+
if (this.collapsed()) {
|
52
|
+
return this;
|
53
|
+
}
|
54
|
+
|
55
|
+
this.row.removeClass("expanded").addClass("collapsed");
|
56
|
+
|
57
|
+
this._hideChildren();
|
58
|
+
this.expander.attr("title", this.settings.stringExpand);
|
59
|
+
|
60
|
+
if (this.initialized && this.settings.onNodeCollapse != null) {
|
61
|
+
this.settings.onNodeCollapse.apply(this);
|
62
|
+
}
|
63
|
+
|
64
|
+
return this;
|
65
|
+
};
|
66
|
+
|
67
|
+
Node.prototype.collapsed = function() {
|
68
|
+
return this.row.hasClass("collapsed");
|
69
|
+
};
|
70
|
+
|
71
|
+
// TODO destroy: remove event handlers, expander, indenter, etc.
|
72
|
+
|
73
|
+
Node.prototype.expand = function() {
|
74
|
+
if (this.expanded()) {
|
75
|
+
return this;
|
76
|
+
}
|
77
|
+
|
78
|
+
this.row.removeClass("collapsed").addClass("expanded");
|
79
|
+
|
80
|
+
if (this.initialized && this.settings.onNodeExpand != null) {
|
81
|
+
this.settings.onNodeExpand.apply(this);
|
82
|
+
}
|
83
|
+
|
84
|
+
if ($(this.row).is(":visible")) {
|
85
|
+
this._showChildren();
|
86
|
+
}
|
87
|
+
|
88
|
+
this.expander.attr("title", this.settings.stringCollapse);
|
89
|
+
|
90
|
+
return this;
|
91
|
+
};
|
92
|
+
|
93
|
+
Node.prototype.expanded = function() {
|
94
|
+
return this.row.hasClass("expanded");
|
95
|
+
};
|
96
|
+
|
97
|
+
Node.prototype.hide = function() {
|
98
|
+
this._hideChildren();
|
99
|
+
this.row.hide();
|
100
|
+
return this;
|
101
|
+
};
|
102
|
+
|
103
|
+
Node.prototype.isBranchNode = function() {
|
104
|
+
if(this.children.length > 0 || this.row.data(this.settings.branchAttr) === true) {
|
105
|
+
return true;
|
106
|
+
} else {
|
107
|
+
return false;
|
108
|
+
}
|
109
|
+
};
|
110
|
+
|
111
|
+
Node.prototype.updateBranchLeafClass = function(){
|
112
|
+
this.row.removeClass('branch');
|
113
|
+
this.row.removeClass('leaf');
|
114
|
+
this.row.addClass(this.isBranchNode() ? 'branch' : 'leaf');
|
115
|
+
};
|
116
|
+
|
117
|
+
Node.prototype.level = function() {
|
118
|
+
return this.ancestors().length;
|
119
|
+
};
|
120
|
+
|
121
|
+
Node.prototype.parentNode = function() {
|
122
|
+
if (this.parentId != null) {
|
123
|
+
return this.tree[this.parentId];
|
124
|
+
} else {
|
125
|
+
return null;
|
126
|
+
}
|
127
|
+
};
|
128
|
+
|
129
|
+
Node.prototype.removeChild = function(child) {
|
130
|
+
var i = $.inArray(child, this.children);
|
131
|
+
return this.children.splice(i, 1)
|
132
|
+
};
|
133
|
+
|
134
|
+
Node.prototype.render = function() {
|
135
|
+
var handler,
|
136
|
+
settings = this.settings,
|
137
|
+
target;
|
138
|
+
|
139
|
+
if (settings.expandable === true && this.isBranchNode()) {
|
140
|
+
handler = function(e) {
|
141
|
+
$(this).parents("table").treetable("node", $(this).parents("tr").data(settings.nodeIdAttr)).toggle();
|
142
|
+
return e.preventDefault();
|
143
|
+
};
|
144
|
+
|
145
|
+
this.indenter.html(this.expander);
|
146
|
+
target = settings.clickableNodeNames === true ? this.treeCell : this.expander;
|
147
|
+
|
148
|
+
target.off("click.treetable").on("click.treetable", handler);
|
149
|
+
target.off("keydown.treetable").on("keydown.treetable", function(e) {
|
150
|
+
if (e.keyCode == 13) {
|
151
|
+
handler.apply(this, [e]);
|
152
|
+
}
|
153
|
+
});
|
154
|
+
}
|
155
|
+
|
156
|
+
this.indenter[0].style.paddingLeft = "" + (this.level() * settings.indent) + "px";
|
157
|
+
|
158
|
+
return this;
|
159
|
+
};
|
160
|
+
|
161
|
+
Node.prototype.reveal = function() {
|
162
|
+
if (this.parentId != null) {
|
163
|
+
this.parentNode().reveal();
|
164
|
+
}
|
165
|
+
return this.expand();
|
166
|
+
};
|
167
|
+
|
168
|
+
Node.prototype.setParent = function(node) {
|
169
|
+
if (this.parentId != null) {
|
170
|
+
this.tree[this.parentId].removeChild(this);
|
171
|
+
}
|
172
|
+
this.parentId = node.id;
|
173
|
+
this.row.data(this.settings.parentIdAttr, node.id);
|
174
|
+
return node.addChild(this);
|
175
|
+
};
|
176
|
+
|
177
|
+
Node.prototype.show = function() {
|
178
|
+
if (!this.initialized) {
|
179
|
+
this._initialize();
|
180
|
+
}
|
181
|
+
this.row.show();
|
182
|
+
if (this.expanded()) {
|
183
|
+
this._showChildren();
|
184
|
+
}
|
185
|
+
return this;
|
186
|
+
};
|
187
|
+
|
188
|
+
Node.prototype.toggle = function() {
|
189
|
+
if (this.expanded()) {
|
190
|
+
this.collapse();
|
191
|
+
} else {
|
192
|
+
this.expand();
|
193
|
+
}
|
194
|
+
return this;
|
195
|
+
};
|
196
|
+
|
197
|
+
Node.prototype._hideChildren = function() {
|
198
|
+
var child, _i, _len, _ref, _results;
|
199
|
+
_ref = this.children;
|
200
|
+
_results = [];
|
201
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
202
|
+
child = _ref[_i];
|
203
|
+
_results.push(child.hide());
|
204
|
+
}
|
205
|
+
return _results;
|
206
|
+
};
|
207
|
+
|
208
|
+
Node.prototype._initialize = function() {
|
209
|
+
var settings = this.settings;
|
210
|
+
|
211
|
+
this.render();
|
212
|
+
|
213
|
+
if (settings.expandable === true && settings.initialState === "collapsed") {
|
214
|
+
this.collapse();
|
215
|
+
} else {
|
216
|
+
this.expand();
|
217
|
+
}
|
218
|
+
|
219
|
+
if (settings.onNodeInitialized != null) {
|
220
|
+
settings.onNodeInitialized.apply(this);
|
221
|
+
}
|
222
|
+
|
223
|
+
return this.initialized = true;
|
224
|
+
};
|
225
|
+
|
226
|
+
Node.prototype._showChildren = function() {
|
227
|
+
var child, _i, _len, _ref, _results;
|
228
|
+
_ref = this.children;
|
229
|
+
_results = [];
|
230
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
231
|
+
child = _ref[_i];
|
232
|
+
_results.push(child.show());
|
233
|
+
}
|
234
|
+
return _results;
|
235
|
+
};
|
236
|
+
|
237
|
+
return Node;
|
238
|
+
})();
|
239
|
+
|
240
|
+
Tree = (function() {
|
241
|
+
function Tree(table, settings) {
|
242
|
+
this.table = table;
|
243
|
+
this.settings = settings;
|
244
|
+
this.tree = {};
|
245
|
+
|
246
|
+
// Cache the nodes and roots in simple arrays for quick access/iteration
|
247
|
+
this.nodes = [];
|
248
|
+
this.roots = [];
|
249
|
+
}
|
250
|
+
|
251
|
+
Tree.prototype.collapseAll = function() {
|
252
|
+
var node, _i, _len, _ref, _results;
|
253
|
+
_ref = this.nodes;
|
254
|
+
_results = [];
|
255
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
256
|
+
node = _ref[_i];
|
257
|
+
_results.push(node.collapse());
|
258
|
+
}
|
259
|
+
return _results;
|
260
|
+
};
|
261
|
+
|
262
|
+
Tree.prototype.expandAll = function() {
|
263
|
+
var node, _i, _len, _ref, _results;
|
264
|
+
_ref = this.nodes;
|
265
|
+
_results = [];
|
266
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
267
|
+
node = _ref[_i];
|
268
|
+
_results.push(node.expand());
|
269
|
+
}
|
270
|
+
return _results;
|
271
|
+
};
|
272
|
+
|
273
|
+
Tree.prototype.findLastNode = function (node) {
|
274
|
+
if (node.children.length > 0) {
|
275
|
+
return this.findLastNode(node.children[node.children.length - 1]);
|
276
|
+
} else {
|
277
|
+
return node;
|
278
|
+
}
|
279
|
+
};
|
280
|
+
|
281
|
+
Tree.prototype.loadRows = function(rows) {
|
282
|
+
var node, row, i;
|
283
|
+
|
284
|
+
if (rows != null) {
|
285
|
+
for (i = 0; i < rows.length; i++) {
|
286
|
+
row = $(rows[i]);
|
287
|
+
|
288
|
+
if (row.data(this.settings.nodeIdAttr) != null) {
|
289
|
+
node = new Node(row, this.tree, this.settings);
|
290
|
+
this.nodes.push(node);
|
291
|
+
this.tree[node.id] = node;
|
292
|
+
|
293
|
+
if (node.parentId != null && this.tree[node.parentId]) {
|
294
|
+
this.tree[node.parentId].addChild(node);
|
295
|
+
} else {
|
296
|
+
this.roots.push(node);
|
297
|
+
}
|
298
|
+
}
|
299
|
+
}
|
300
|
+
}
|
301
|
+
|
302
|
+
for (i = 0; i < this.nodes.length; i++) {
|
303
|
+
node = this.nodes[i].updateBranchLeafClass();
|
304
|
+
}
|
305
|
+
|
306
|
+
return this;
|
307
|
+
};
|
308
|
+
|
309
|
+
Tree.prototype.move = function(node, destination) {
|
310
|
+
// Conditions:
|
311
|
+
// 1: +node+ should not be inserted as a child of +node+ itself.
|
312
|
+
// 2: +destination+ should not be the same as +node+'s current parent (this
|
313
|
+
// prevents +node+ from being moved to the same location where it already
|
314
|
+
// is).
|
315
|
+
// 3: +node+ should not be inserted in a location in a branch if this would
|
316
|
+
// result in +node+ being an ancestor of itself.
|
317
|
+
var nodeParent = node.parentNode();
|
318
|
+
if (node !== destination && destination.id !== node.parentId && $.inArray(node, destination.ancestors()) === -1) {
|
319
|
+
node.setParent(destination);
|
320
|
+
this._moveRows(node, destination);
|
321
|
+
|
322
|
+
// Re-render parentNode if this is its first child node, and therefore
|
323
|
+
// doesn't have the expander yet.
|
324
|
+
if (node.parentNode().children.length === 1) {
|
325
|
+
node.parentNode().render();
|
326
|
+
}
|
327
|
+
}
|
328
|
+
|
329
|
+
if(nodeParent){
|
330
|
+
nodeParent.updateBranchLeafClass();
|
331
|
+
}
|
332
|
+
if(node.parentNode()){
|
333
|
+
node.parentNode().updateBranchLeafClass();
|
334
|
+
}
|
335
|
+
node.updateBranchLeafClass();
|
336
|
+
return this;
|
337
|
+
};
|
338
|
+
|
339
|
+
Tree.prototype.removeNode = function(node) {
|
340
|
+
// Recursively remove all descendants of +node+
|
341
|
+
this.unloadBranch(node);
|
342
|
+
|
343
|
+
// Remove node from DOM (<tr>)
|
344
|
+
node.row.remove();
|
345
|
+
|
346
|
+
// Remove node from parent children list
|
347
|
+
if (node.parentId != null) {
|
348
|
+
node.parentNode().removeChild(node);
|
349
|
+
}
|
350
|
+
|
351
|
+
// Clean up Tree object (so Node objects are GC-ed)
|
352
|
+
delete this.tree[node.id];
|
353
|
+
this.nodes.splice($.inArray(node, this.nodes), 1);
|
354
|
+
|
355
|
+
return this;
|
356
|
+
}
|
357
|
+
|
358
|
+
Tree.prototype.render = function() {
|
359
|
+
var root, _i, _len, _ref;
|
360
|
+
_ref = this.roots;
|
361
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
362
|
+
root = _ref[_i];
|
363
|
+
|
364
|
+
// Naming is confusing (show/render). I do not call render on node from
|
365
|
+
// here.
|
366
|
+
root.show();
|
367
|
+
}
|
368
|
+
return this;
|
369
|
+
};
|
370
|
+
|
371
|
+
Tree.prototype.sortBranch = function(node, sortFun) {
|
372
|
+
// First sort internal array of children
|
373
|
+
node.children.sort(sortFun);
|
374
|
+
|
375
|
+
// Next render rows in correct order on page
|
376
|
+
this._sortChildRows(node);
|
377
|
+
|
378
|
+
return this;
|
379
|
+
};
|
380
|
+
|
381
|
+
Tree.prototype.unloadBranch = function(node) {
|
382
|
+
// Use a copy of the children array to not have other functions interfere
|
383
|
+
// with this function if they manipulate the children array
|
384
|
+
// (eg removeNode).
|
385
|
+
var children = node.children.slice(0),
|
386
|
+
i;
|
387
|
+
|
388
|
+
for (i = 0; i < children.length; i++) {
|
389
|
+
this.removeNode(children[i]);
|
390
|
+
}
|
391
|
+
|
392
|
+
// Reset node's collection of children
|
393
|
+
node.children = [];
|
394
|
+
|
395
|
+
node.updateBranchLeafClass();
|
396
|
+
|
397
|
+
return this;
|
398
|
+
};
|
399
|
+
|
400
|
+
Tree.prototype._moveRows = function(node, destination) {
|
401
|
+
var children = node.children, i;
|
402
|
+
|
403
|
+
node.row.insertAfter(destination.row);
|
404
|
+
node.render();
|
405
|
+
|
406
|
+
// Loop backwards through children to have them end up on UI in correct
|
407
|
+
// order (see #112)
|
408
|
+
for (i = children.length - 1; i >= 0; i--) {
|
409
|
+
this._moveRows(children[i], node);
|
410
|
+
}
|
411
|
+
};
|
412
|
+
|
413
|
+
// Special _moveRows case, move children to itself to force sorting
|
414
|
+
Tree.prototype._sortChildRows = function(parentNode) {
|
415
|
+
return this._moveRows(parentNode, parentNode);
|
416
|
+
};
|
417
|
+
|
418
|
+
return Tree;
|
419
|
+
})();
|
420
|
+
|
421
|
+
// jQuery Plugin
|
422
|
+
methods = {
|
423
|
+
init: function(options, force) {
|
424
|
+
var settings;
|
425
|
+
|
426
|
+
settings = $.extend({
|
427
|
+
branchAttr: "ttBranch",
|
428
|
+
clickableNodeNames: false,
|
429
|
+
column: 0,
|
430
|
+
columnElType: "td", // i.e. 'td', 'th' or 'td,th'
|
431
|
+
expandable: false,
|
432
|
+
expanderTemplate: "<a href='#'> </a>",
|
433
|
+
indent: 19,
|
434
|
+
indenterTemplate: "<span class='indenter'></span>",
|
435
|
+
initialState: "collapsed",
|
436
|
+
nodeIdAttr: "ttId", // maps to data-tt-id
|
437
|
+
parentIdAttr: "ttParentId", // maps to data-tt-parent-id
|
438
|
+
stringExpand: "Expand",
|
439
|
+
stringCollapse: "Collapse",
|
440
|
+
|
441
|
+
// Events
|
442
|
+
onInitialized: null,
|
443
|
+
onNodeCollapse: null,
|
444
|
+
onNodeExpand: null,
|
445
|
+
onNodeInitialized: null
|
446
|
+
}, options);
|
447
|
+
|
448
|
+
return this.each(function() {
|
449
|
+
var el = $(this), tree;
|
450
|
+
|
451
|
+
if (force || el.data("treetable") === undefined) {
|
452
|
+
tree = new Tree(this, settings);
|
453
|
+
tree.loadRows(this.rows).render();
|
454
|
+
|
455
|
+
el.addClass("treetable").data("treetable", tree);
|
456
|
+
|
457
|
+
if (settings.onInitialized != null) {
|
458
|
+
settings.onInitialized.apply(tree);
|
459
|
+
}
|
460
|
+
}
|
461
|
+
|
462
|
+
return el;
|
463
|
+
});
|
464
|
+
},
|
465
|
+
|
466
|
+
destroy: function() {
|
467
|
+
return this.each(function() {
|
468
|
+
return $(this).removeData("treetable").removeClass("treetable");
|
469
|
+
});
|
470
|
+
},
|
471
|
+
|
472
|
+
collapseAll: function() {
|
473
|
+
this.data("treetable").collapseAll();
|
474
|
+
return this;
|
475
|
+
},
|
476
|
+
|
477
|
+
collapseNode: function(id) {
|
478
|
+
var node = this.data("treetable").tree[id];
|
479
|
+
|
480
|
+
if (node) {
|
481
|
+
node.collapse();
|
482
|
+
} else {
|
483
|
+
throw new Error("Unknown node '" + id + "'");
|
484
|
+
}
|
485
|
+
|
486
|
+
return this;
|
487
|
+
},
|
488
|
+
|
489
|
+
expandAll: function() {
|
490
|
+
this.data("treetable").expandAll();
|
491
|
+
return this;
|
492
|
+
},
|
493
|
+
|
494
|
+
expandNode: function(id) {
|
495
|
+
var node = this.data("treetable").tree[id];
|
496
|
+
|
497
|
+
if (node) {
|
498
|
+
if (!node.initialized) {
|
499
|
+
node._initialize();
|
500
|
+
}
|
501
|
+
|
502
|
+
node.expand();
|
503
|
+
} else {
|
504
|
+
throw new Error("Unknown node '" + id + "'");
|
505
|
+
}
|
506
|
+
|
507
|
+
return this;
|
508
|
+
},
|
509
|
+
|
510
|
+
loadBranch: function(node, rows) {
|
511
|
+
var settings = this.data("treetable").settings,
|
512
|
+
tree = this.data("treetable").tree;
|
513
|
+
|
514
|
+
// TODO Switch to $.parseHTML
|
515
|
+
rows = $(rows);
|
516
|
+
|
517
|
+
if (node == null) { // Inserting new root nodes
|
518
|
+
this.append(rows);
|
519
|
+
} else {
|
520
|
+
var lastNode = this.data("treetable").findLastNode(node);
|
521
|
+
rows.insertAfter(lastNode.row);
|
522
|
+
}
|
523
|
+
|
524
|
+
this.data("treetable").loadRows(rows);
|
525
|
+
|
526
|
+
// Make sure nodes are properly initialized
|
527
|
+
rows.filter("tr").each(function() {
|
528
|
+
tree[$(this).data(settings.nodeIdAttr)].show();
|
529
|
+
});
|
530
|
+
|
531
|
+
if (node != null) {
|
532
|
+
// Re-render parent to ensure expander icon is shown (#79)
|
533
|
+
node.render().expand();
|
534
|
+
}
|
535
|
+
|
536
|
+
return this;
|
537
|
+
},
|
538
|
+
|
539
|
+
move: function(nodeId, destinationId) {
|
540
|
+
var destination, node;
|
541
|
+
|
542
|
+
node = this.data("treetable").tree[nodeId];
|
543
|
+
destination = this.data("treetable").tree[destinationId];
|
544
|
+
this.data("treetable").move(node, destination);
|
545
|
+
|
546
|
+
return this;
|
547
|
+
},
|
548
|
+
|
549
|
+
node: function(id) {
|
550
|
+
return this.data("treetable").tree[id];
|
551
|
+
},
|
552
|
+
|
553
|
+
removeNode: function(id) {
|
554
|
+
var node = this.data("treetable").tree[id];
|
555
|
+
|
556
|
+
if (node) {
|
557
|
+
this.data("treetable").removeNode(node);
|
558
|
+
} else {
|
559
|
+
throw new Error("Unknown node '" + id + "'");
|
560
|
+
}
|
561
|
+
|
562
|
+
return this;
|
563
|
+
},
|
564
|
+
|
565
|
+
reveal: function(id) {
|
566
|
+
var node = this.data("treetable").tree[id];
|
567
|
+
|
568
|
+
if (node) {
|
569
|
+
node.reveal();
|
570
|
+
} else {
|
571
|
+
throw new Error("Unknown node '" + id + "'");
|
572
|
+
}
|
573
|
+
|
574
|
+
return this;
|
575
|
+
},
|
576
|
+
|
577
|
+
sortBranch: function(node, columnOrFunction) {
|
578
|
+
var settings = this.data("treetable").settings,
|
579
|
+
prepValue,
|
580
|
+
sortFun;
|
581
|
+
|
582
|
+
columnOrFunction = columnOrFunction || settings.column;
|
583
|
+
sortFun = columnOrFunction;
|
584
|
+
|
585
|
+
if ($.isNumeric(columnOrFunction)) {
|
586
|
+
sortFun = function(a, b) {
|
587
|
+
var extractValue, valA, valB;
|
588
|
+
|
589
|
+
extractValue = function(node) {
|
590
|
+
var val = node.row.find("td:eq(" + columnOrFunction + ")").text();
|
591
|
+
// Ignore trailing/leading whitespace and use uppercase values for
|
592
|
+
// case insensitive ordering
|
593
|
+
return $.trim(val).toUpperCase();
|
594
|
+
}
|
595
|
+
|
596
|
+
valA = extractValue(a);
|
597
|
+
valB = extractValue(b);
|
598
|
+
|
599
|
+
if (valA < valB) return -1;
|
600
|
+
if (valA > valB) return 1;
|
601
|
+
return 0;
|
602
|
+
};
|
603
|
+
}
|
604
|
+
|
605
|
+
this.data("treetable").sortBranch(node, sortFun);
|
606
|
+
return this;
|
607
|
+
},
|
608
|
+
|
609
|
+
unloadBranch: function(node) {
|
610
|
+
this.data("treetable").unloadBranch(node);
|
611
|
+
return this;
|
612
|
+
}
|
613
|
+
};
|
614
|
+
|
615
|
+
$.fn.treetable = function(method) {
|
616
|
+
if (methods[method]) {
|
617
|
+
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
|
618
|
+
} else if (typeof method === 'object' || !method) {
|
619
|
+
return methods.init.apply(this, arguments);
|
620
|
+
} else {
|
621
|
+
return $.error("Method " + method + " does not exist on jQuery.treetable");
|
622
|
+
}
|
623
|
+
};
|
624
|
+
|
625
|
+
// Expose classes to world
|
626
|
+
this.TreeTable || (this.TreeTable = {});
|
627
|
+
this.TreeTable.Node = Node;
|
628
|
+
this.TreeTable.Tree = Tree;
|
629
|
+
})(jQuery);
|