dm-is-select 0.0.9 → 1.2.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.
- data/.gitignore +36 -12
- data/Gemfile +23 -0
- data/Guardfile +13 -0
- data/{LICENSE → LICENSE.txt} +1 -1
- data/README.rdoc +32 -7
- data/Rakefile +33 -69
- data/VERSION +1 -1
- data/dm-is-select.gemspec +94 -44
- data/docs/apple-touch-icon.png +0 -0
- data/docs/classes/DataMapper.html +81 -0
- data/docs/classes/DataMapper/Is.html +81 -0
- data/docs/classes/DataMapper/Is/Select.html +205 -0
- data/docs/classes/DataMapper/Is/Select/ClassMethods.html +258 -0
- data/docs/created.rid +4 -0
- data/docs/css/github.css +129 -0
- data/docs/css/main.css +333 -0
- data/docs/css/panel.css +384 -0
- data/docs/css/reset.css +48 -0
- data/docs/favicon.ico +0 -0
- data/docs/files/README_rdoc.html +272 -0
- data/docs/files/lib/dm-is-select_rb.html +88 -0
- data/docs/files/lib/is/select_rb.html +94 -0
- data/docs/i/arrows.png +0 -0
- data/docs/i/results_bg.png +0 -0
- data/docs/i/tree_bg.png +0 -0
- data/docs/index.html +13 -0
- data/docs/js/highlight.pack.js +1 -0
- data/docs/js/jquery-1.3.2.min.js +19 -0
- data/docs/js/jquery-effect.js +593 -0
- data/docs/js/main.js +24 -0
- data/docs/js/navigation.js +142 -0
- data/docs/js/search_index.js +1 -0
- data/docs/js/searchdoc.js +449 -0
- data/docs/js/searcher.js +228 -0
- data/docs/panel/index.html +73 -0
- data/docs/panel/links.html +12 -0
- data/docs/panel/tree.js +1 -0
- data/lib/dm-is-select.rb +5 -11
- data/lib/{dm-is-select/is → is}/select.rb +12 -10
- data/spec/{integration/select_spec.rb → dm-is-select_spec.rb} +25 -13
- data/spec/spec_helper.rb +10 -8
- metadata +278 -94
- data/History.rdoc +0 -24
- data/TODO +0 -0
- data/lib/dm-is-select/is/version.rb +0 -7
data/docs/js/searcher.js
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
Searcher = function(data) {
|
2
|
+
this.data = data;
|
3
|
+
this.handlers = [];
|
4
|
+
}
|
5
|
+
|
6
|
+
Searcher.prototype = new function() {
|
7
|
+
// search is performed in chunks of 1000 for non-blocking user input
|
8
|
+
var CHUNK_SIZE = 1000;
|
9
|
+
// do not try to find more than 100 results
|
10
|
+
var MAX_RESULTS = 100;
|
11
|
+
var huid = 1;
|
12
|
+
var suid = 1;
|
13
|
+
var runs = 0;
|
14
|
+
|
15
|
+
this.find = function(query) {
|
16
|
+
var queries = splitQuery(query);
|
17
|
+
var regexps = buildRegexps(queries);
|
18
|
+
var highlighters = buildHilighters(queries);
|
19
|
+
var state = { from: 0, pass: 0, limit: MAX_RESULTS, n: suid++};
|
20
|
+
var _this = this;
|
21
|
+
|
22
|
+
this.currentSuid = state.n;
|
23
|
+
|
24
|
+
if (!query) return;
|
25
|
+
|
26
|
+
var run = function() {
|
27
|
+
// stop current search thread if new search started
|
28
|
+
if (state.n != _this.currentSuid) return;
|
29
|
+
|
30
|
+
var results =
|
31
|
+
performSearch(_this.data, regexps, queries, highlighters, state);
|
32
|
+
var hasMore = (state.limit > 0 && state.pass < 4);
|
33
|
+
|
34
|
+
triggerResults.call(_this, results, !hasMore);
|
35
|
+
if (hasMore) {
|
36
|
+
setTimeout(run, 2);
|
37
|
+
}
|
38
|
+
runs++;
|
39
|
+
};
|
40
|
+
runs = 0;
|
41
|
+
|
42
|
+
// start search thread
|
43
|
+
run();
|
44
|
+
}
|
45
|
+
|
46
|
+
/* ----- Events ------ */
|
47
|
+
this.ready = function(fn) {
|
48
|
+
fn.huid = huid;
|
49
|
+
this.handlers.push(fn);
|
50
|
+
}
|
51
|
+
|
52
|
+
/* ----- Utilities ------ */
|
53
|
+
function splitQuery(query) {
|
54
|
+
return jQuery.grep(query.split(/(\s+|::?|\(\)?)/), function(string) {
|
55
|
+
return string.match(/\S/)
|
56
|
+
});
|
57
|
+
}
|
58
|
+
|
59
|
+
function buildRegexps(queries) {
|
60
|
+
return jQuery.map(queries, function(query) {
|
61
|
+
return new RegExp(query.replace(/(.)/g, '([$1])([^$1]*?)'), 'i')
|
62
|
+
});
|
63
|
+
}
|
64
|
+
|
65
|
+
function buildHilighters(queries) {
|
66
|
+
return jQuery.map(queries, function(query) {
|
67
|
+
return jQuery.map(query.split(''), function(l, i) {
|
68
|
+
return '\u0001$' + (i*2+1) + '\u0002$' + (i*2+2);
|
69
|
+
}).join('');
|
70
|
+
});
|
71
|
+
}
|
72
|
+
|
73
|
+
// function longMatchRegexp(index, longIndex, regexps) {
|
74
|
+
// for (var i = regexps.length - 1; i >= 0; i--){
|
75
|
+
// if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) return false;
|
76
|
+
// };
|
77
|
+
// return true;
|
78
|
+
// }
|
79
|
+
|
80
|
+
|
81
|
+
/* ----- Mathchers ------ */
|
82
|
+
|
83
|
+
/*
|
84
|
+
* This record matches if the index starts with queries[0] and the record
|
85
|
+
* matches all of the regexps
|
86
|
+
*/
|
87
|
+
function matchPassBeginning(index, longIndex, queries, regexps) {
|
88
|
+
if (index.indexOf(queries[0]) != 0) return false;
|
89
|
+
for (var i=1, l = regexps.length; i < l; i++) {
|
90
|
+
if (!index.match(regexps[i]) && !longIndex.match(regexps[i]))
|
91
|
+
return false;
|
92
|
+
};
|
93
|
+
return true;
|
94
|
+
}
|
95
|
+
|
96
|
+
/*
|
97
|
+
* This record matches if the longIndex starts with queries[0] and the
|
98
|
+
* longIndex matches all of the regexps
|
99
|
+
*/
|
100
|
+
function matchPassLongIndex(index, longIndex, queries, regexps) {
|
101
|
+
if (longIndex.indexOf(queries[0]) != 0) return false;
|
102
|
+
for (var i=1, l = regexps.length; i < l; i++) {
|
103
|
+
if (!longIndex.match(regexps[i]))
|
104
|
+
return false;
|
105
|
+
};
|
106
|
+
return true;
|
107
|
+
}
|
108
|
+
|
109
|
+
/*
|
110
|
+
* This record matches if the index contains queries[0] and the record
|
111
|
+
* matches all of the regexps
|
112
|
+
*/
|
113
|
+
function matchPassContains(index, longIndex, queries, regexps) {
|
114
|
+
if (index.indexOf(queries[0]) == -1) return false;
|
115
|
+
for (var i=1, l = regexps.length; i < l; i++) {
|
116
|
+
if (!index.match(regexps[i]) && !longIndex.match(regexps[i]))
|
117
|
+
return false;
|
118
|
+
};
|
119
|
+
return true;
|
120
|
+
}
|
121
|
+
|
122
|
+
/*
|
123
|
+
* This record matches if regexps[0] matches the index and the record
|
124
|
+
* matches all of the regexps
|
125
|
+
*/
|
126
|
+
function matchPassRegexp(index, longIndex, queries, regexps) {
|
127
|
+
if (!index.match(regexps[0])) return false;
|
128
|
+
for (var i=1, l = regexps.length; i < l; i++) {
|
129
|
+
if (!index.match(regexps[i]) && !longIndex.match(regexps[i]))
|
130
|
+
return false;
|
131
|
+
};
|
132
|
+
return true;
|
133
|
+
}
|
134
|
+
|
135
|
+
|
136
|
+
/* ----- Highlighters ------ */
|
137
|
+
function highlightRegexp(info, queries, regexps, highlighters) {
|
138
|
+
var result = createResult(info);
|
139
|
+
for (var i=0, l = regexps.length; i < l; i++) {
|
140
|
+
result.title = result.title.replace(regexps[i], highlighters[i]);
|
141
|
+
result.namespace = result.namespace.replace(regexps[i], highlighters[i]);
|
142
|
+
};
|
143
|
+
return result;
|
144
|
+
}
|
145
|
+
|
146
|
+
function hltSubstring(string, pos, length) {
|
147
|
+
return string.substring(0, pos) + '\u0001' + string.substring(pos, pos + length) + '\u0002' + string.substring(pos + length);
|
148
|
+
}
|
149
|
+
|
150
|
+
function highlightQuery(info, queries, regexps, highlighters) {
|
151
|
+
var result = createResult(info);
|
152
|
+
var pos = 0;
|
153
|
+
var lcTitle = result.title.toLowerCase();
|
154
|
+
|
155
|
+
pos = lcTitle.indexOf(queries[0]);
|
156
|
+
if (pos != -1) {
|
157
|
+
result.title = hltSubstring(result.title, pos, queries[0].length);
|
158
|
+
}
|
159
|
+
|
160
|
+
result.namespace = result.namespace.replace(regexps[0], highlighters[0]);
|
161
|
+
for (var i=1, l = regexps.length; i < l; i++) {
|
162
|
+
result.title = result.title.replace(regexps[i], highlighters[i]);
|
163
|
+
result.namespace = result.namespace.replace(regexps[i], highlighters[i]);
|
164
|
+
};
|
165
|
+
return result;
|
166
|
+
}
|
167
|
+
|
168
|
+
function createResult(info) {
|
169
|
+
var result = {};
|
170
|
+
result.title = info[0];
|
171
|
+
result.namespace = info[1];
|
172
|
+
result.path = info[2];
|
173
|
+
result.params = info[3];
|
174
|
+
result.snippet = info[4];
|
175
|
+
return result;
|
176
|
+
}
|
177
|
+
|
178
|
+
/* ----- Searching ------ */
|
179
|
+
function performSearch(data, regexps, queries, highlighters, state) {
|
180
|
+
var searchIndex = data.searchIndex;
|
181
|
+
var longSearchIndex = data.longSearchIndex;
|
182
|
+
var info = data.info;
|
183
|
+
var result = [];
|
184
|
+
var i = state.from;
|
185
|
+
var l = searchIndex.length;
|
186
|
+
var togo = CHUNK_SIZE;
|
187
|
+
var matchFunc, hltFunc;
|
188
|
+
|
189
|
+
while (state.pass < 4 && state.limit > 0 && togo > 0) {
|
190
|
+
if (state.pass == 0) {
|
191
|
+
matchFunc = matchPassBeginning;
|
192
|
+
hltFunc = highlightQuery;
|
193
|
+
} else if (state.pass == 1) {
|
194
|
+
matchFunc = matchPassLongIndex;
|
195
|
+
hltFunc = highlightQuery;
|
196
|
+
} else if (state.pass == 2) {
|
197
|
+
matchFunc = matchPassContains;
|
198
|
+
hltFunc = highlightQuery;
|
199
|
+
} else if (state.pass == 3) {
|
200
|
+
matchFunc = matchPassRegexp;
|
201
|
+
hltFunc = highlightRegexp;
|
202
|
+
}
|
203
|
+
|
204
|
+
for (; togo > 0 && i < l && state.limit > 0; i++, togo--) {
|
205
|
+
if (info[i].n == state.n) continue;
|
206
|
+
if (matchFunc(searchIndex[i], longSearchIndex[i], queries, regexps)) {
|
207
|
+
info[i].n = state.n;
|
208
|
+
result.push(hltFunc(info[i], queries, regexps, highlighters));
|
209
|
+
state.limit--;
|
210
|
+
}
|
211
|
+
};
|
212
|
+
if (searchIndex.length <= i) {
|
213
|
+
state.pass++;
|
214
|
+
i = state.from = 0;
|
215
|
+
} else {
|
216
|
+
state.from = i;
|
217
|
+
}
|
218
|
+
}
|
219
|
+
return result;
|
220
|
+
}
|
221
|
+
|
222
|
+
function triggerResults(results, isLast) {
|
223
|
+
jQuery.each(this.handlers, function(i, fn) {
|
224
|
+
fn.call(this, results, isLast)
|
225
|
+
})
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
3
|
+
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
4
|
+
|
5
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
6
|
+
<head>
|
7
|
+
<title>search index</title>
|
8
|
+
<link rel="stylesheet" href="../css/reset.css" type="text/css" media="screen" charset="utf-8" />
|
9
|
+
<link rel="stylesheet" href="../css/panel.css" type="text/css" media="screen" charset="utf-8" />
|
10
|
+
<script src="../js/search_index.js" type="text/javascript" charset="utf-8"></script>
|
11
|
+
<script src="../js/searcher.js" type="text/javascript" charset="utf-8"></script>
|
12
|
+
<script src="tree.js" type="text/javascript" charset="utf-8"></script>
|
13
|
+
<script src="../js/jquery-1.3.2.min.js" type="text/javascript" charset="utf-8"></script>
|
14
|
+
<script src="../js/searchdoc.js" type="text/javascript" charset="utf-8"></script>
|
15
|
+
<script type="text/javascript" charset="utf-8">
|
16
|
+
function placeholder() {
|
17
|
+
if ($('<input type="text">')[0].placeholder !== undefined) return;
|
18
|
+
|
19
|
+
$('#search-label').click(function() {
|
20
|
+
$('#search').focus();
|
21
|
+
$('#search-label').hide();
|
22
|
+
});
|
23
|
+
|
24
|
+
$('#search').focus(function() {
|
25
|
+
$('#search-label').hide();
|
26
|
+
});
|
27
|
+
$('#search').blur(function() {
|
28
|
+
this.value == '' && $('#search-label').show()
|
29
|
+
});
|
30
|
+
|
31
|
+
$('#search')[0].value == '' && $('#search-label').show();
|
32
|
+
}
|
33
|
+
$(function() {
|
34
|
+
placeholder();
|
35
|
+
$('#links').hide();
|
36
|
+
var panel = new Searchdoc.Panel($('#panel'), search_data, tree, window.parent.frames[1]);
|
37
|
+
$('#search').focus();
|
38
|
+
|
39
|
+
var s = window.parent.location.search.match(/\?q=([^&]+)/);
|
40
|
+
if (s) {
|
41
|
+
s = decodeURIComponent(s[1]).replace(/\+/g, ' ');
|
42
|
+
if (s.length > 0)
|
43
|
+
{
|
44
|
+
$('#search').val(s);
|
45
|
+
panel.search(s, true);
|
46
|
+
}
|
47
|
+
}
|
48
|
+
})
|
49
|
+
</script>
|
50
|
+
</head>
|
51
|
+
<body>
|
52
|
+
<div class="panel panel_tree" id="panel">
|
53
|
+
<div class="header">
|
54
|
+
<div>
|
55
|
+
<label for="search" id="search-label" style="display: none">Search</label>
|
56
|
+
<table>
|
57
|
+
<tr><td>
|
58
|
+
<input type="Search" placeholder="Search" autosave="searchdoc" results="10" id="search" autocomplete="off"/>
|
59
|
+
</td></tr>
|
60
|
+
</table></div>
|
61
|
+
</div>
|
62
|
+
<div class="tree">
|
63
|
+
<ul>
|
64
|
+
</ul>
|
65
|
+
</div>
|
66
|
+
<div class="result">
|
67
|
+
<ul>
|
68
|
+
</ul>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
<a href="links.html" id="links">index</a>
|
72
|
+
</body>
|
73
|
+
</html>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<html>
|
2
|
+
<head>File index</head>
|
3
|
+
<body>
|
4
|
+
|
5
|
+
<a href="../files/README_rdoc.html">README.rdoc</a>
|
6
|
+
|
7
|
+
<a href="../files/lib/dm-is-select_rb.html">lib/dm-is-select.rb</a>
|
8
|
+
|
9
|
+
<a href="../files/lib/is/select_rb.html">lib/is/select.rb</a>
|
10
|
+
|
11
|
+
</body>
|
12
|
+
</html>
|
data/docs/panel/tree.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
var tree = [["","","files",[["README.rdoc","files/README_rdoc.html","",[]],["","","lib",[["dm-is-select.rb","files/lib/dm-is-select_rb.html","",[]],["","","is",[["select.rb","files/lib/is/select_rb.html","",[]]]]]]]],["DataMapper","classes/DataMapper.html","",[["Is","classes/DataMapper/Is.html","",[["Select","classes/DataMapper/Is/Select.html","",[["ClassMethods","classes/DataMapper/Is/Select/ClassMethods.html","",[]]]]]]]]]
|
data/lib/dm-is-select.rb
CHANGED
@@ -1,16 +1,10 @@
|
|
1
|
-
# Needed to import datamapper and other gems
|
2
|
-
require 'rubygems'
|
3
|
-
require 'pathname'
|
4
|
-
|
5
1
|
# Add all external dependencies for the plugin here
|
6
|
-
require '
|
2
|
+
require 'data_mapper'
|
7
3
|
require 'dm-is-tree'
|
4
|
+
require 'active_support/core_ext/hash/except' unless {}.respond_to?(:except)
|
5
|
+
require 'active_support/core_ext/hash/slice' unless {}.respond_to?(:slice)
|
6
|
+
|
8
7
|
# Require plugin-files
|
9
|
-
|
8
|
+
require_relative './is/select'
|
10
9
|
|
11
10
|
DataMapper::Model.append_extensions(DataMapper::Is::Select)
|
12
|
-
|
13
|
-
# If you wish to add methods to all DM Models, use this:
|
14
|
-
#
|
15
|
-
# DataMapper::Model.append_inclusions(DataMapper::Is::Select::ResourceInstanceMethods)
|
16
|
-
#
|
@@ -8,7 +8,8 @@ module DataMapper
|
|
8
8
|
#
|
9
9
|
#
|
10
10
|
#
|
11
|
-
module Select
|
11
|
+
module Select
|
12
|
+
VERSION = IO.read("#{File.dirname(__FILE__)}/../../VERSION").chomp
|
12
13
|
|
13
14
|
##
|
14
15
|
# Defines the field to use for the select menu
|
@@ -100,10 +101,11 @@ module DataMapper
|
|
100
101
|
# @api public
|
101
102
|
def items_for_select_menu(options = {})
|
102
103
|
# clean out the various parts
|
103
|
-
html_options = options.
|
104
|
-
sql_options
|
104
|
+
html_options = options.slice(:prompt, :divider, :show_root, :root_text)
|
105
|
+
sql_options = options.except(:prompt, :divider, :show_root, :root_text)
|
105
106
|
|
106
|
-
|
107
|
+
# defaults
|
108
|
+
html_options = {
|
107
109
|
:prompt => "Select #{self.name}",
|
108
110
|
:divider => true,
|
109
111
|
:show_root => true,
|
@@ -117,13 +119,13 @@ module DataMapper
|
|
117
119
|
mi = self.select_options[:is_tree] ? all({ :parent_id => nil }.merge(sql_options) ) : all(sql_options)
|
118
120
|
|
119
121
|
res = []
|
120
|
-
if
|
121
|
-
res << [nil,
|
122
|
-
res << ['nil', " ------ "] if
|
122
|
+
if html_options[:prompt]
|
123
|
+
res << [nil, html_options[:prompt]]
|
124
|
+
res << ['nil', " ------ "] if html_options[:divider]
|
123
125
|
if self.select_options[:is_tree]
|
124
|
-
if
|
125
|
-
res << [0,
|
126
|
-
res << ['nil'," ------ "] if
|
126
|
+
if html_options[:show_root]
|
127
|
+
res << [0, html_options[:root_text]]
|
128
|
+
res << ['nil'," ------ "] if html_options[:divider]
|
127
129
|
end
|
128
130
|
end
|
129
131
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
|
2
|
-
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
1
|
+
require_relative './spec_helper'
|
3
2
|
|
4
3
|
if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
|
4
|
+
|
5
5
|
describe 'DataMapper::Is::Select' do
|
6
6
|
|
7
7
|
before(:each) do
|
@@ -39,6 +39,13 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
|
|
39
39
|
|
40
40
|
end
|
41
41
|
|
42
|
+
describe "VERSION" do
|
43
|
+
|
44
|
+
it "should have a VERSION constant" do
|
45
|
+
DataMapper::Is::Select::VERSION.should match(/\d\.\d+\.\d+/)
|
46
|
+
end
|
47
|
+
|
48
|
+
end #/ VERSION
|
42
49
|
|
43
50
|
describe "Class Methods" do
|
44
51
|
|
@@ -62,14 +69,14 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
|
|
62
69
|
it "should raise an ArgumentError if non-existant field is provided" do
|
63
70
|
lambda {
|
64
71
|
class Dummy
|
65
|
-
|
66
|
-
|
67
|
-
|
72
|
+
include DataMapper::Resource
|
73
|
+
property :id, Serial
|
74
|
+
property :name, String
|
75
|
+
|
76
|
+
is :select, :does_not_exist
|
77
|
+
end
|
68
78
|
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
DataMapper.auto_migrate!
|
79
|
+
DataMapper.auto_migrate!
|
73
80
|
}.should raise_error(ArgumentError)
|
74
81
|
end
|
75
82
|
|
@@ -317,10 +324,15 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
|
|
317
324
|
|
318
325
|
end #/ Class Methods
|
319
326
|
|
320
|
-
|
321
|
-
|
322
|
-
|
327
|
+
describe "Instance Methods" do
|
328
|
+
|
329
|
+
it "are not defined with this DataMapper plugin" do
|
330
|
+
1.should == 1
|
331
|
+
end
|
332
|
+
|
333
|
+
|
334
|
+
end #/ Instance Methods
|
323
335
|
|
324
336
|
end #/ DataMapper::Is::Select
|
325
337
|
|
326
|
-
end #/ if
|
338
|
+
end #/ if
|