yafowil.widget.autocomplete 2.0a1__py3-none-any.whl
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.
- yafowil/widget/autocomplete/__init__.py +64 -0
- yafowil/widget/autocomplete/example.py +127 -0
- yafowil/widget/autocomplete/resources/widget.css +29 -0
- yafowil/widget/autocomplete/resources/widget.js +340 -0
- yafowil/widget/autocomplete/resources/widget.min.js +1 -0
- yafowil/widget/autocomplete/tests.py +165 -0
- yafowil/widget/autocomplete/widget.py +82 -0
- yafowil.widget.autocomplete-2.0a1-py3.9-nspkg.pth +2 -0
- yafowil.widget.autocomplete-2.0a1.dist-info/LICENSE.rst +27 -0
- yafowil.widget.autocomplete-2.0a1.dist-info/METADATA +186 -0
- yafowil.widget.autocomplete-2.0a1.dist-info/RECORD +15 -0
- yafowil.widget.autocomplete-2.0a1.dist-info/WHEEL +5 -0
- yafowil.widget.autocomplete-2.0a1.dist-info/entry_points.txt +3 -0
- yafowil.widget.autocomplete-2.0a1.dist-info/namespace_packages.txt +2 -0
- yafowil.widget.autocomplete-2.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from yafowil.base import factory
|
|
2
|
+
from yafowil.utils import entry_point
|
|
3
|
+
import os
|
|
4
|
+
import webresource as wr
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
resources_dir = os.path.join(os.path.dirname(__file__), 'resources')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
##############################################################################
|
|
11
|
+
# Default
|
|
12
|
+
##############################################################################
|
|
13
|
+
|
|
14
|
+
# webresource ################################################################
|
|
15
|
+
|
|
16
|
+
resources = wr.ResourceGroup(
|
|
17
|
+
name='yafowil.widget.autocomplete',
|
|
18
|
+
directory=resources_dir,
|
|
19
|
+
path='yafowil-autocomplete'
|
|
20
|
+
)
|
|
21
|
+
resources.add(wr.ScriptResource(
|
|
22
|
+
name='yafowil-autocomplete-js',
|
|
23
|
+
depends='jquery-js',
|
|
24
|
+
resource='widget.js',
|
|
25
|
+
compressed='widget.min.js'
|
|
26
|
+
))
|
|
27
|
+
resources.add(wr.StyleResource(
|
|
28
|
+
name='yafowil-autocomplete-css',
|
|
29
|
+
resource='widget.css'
|
|
30
|
+
))
|
|
31
|
+
|
|
32
|
+
# B/C resources ##############################################################
|
|
33
|
+
|
|
34
|
+
js = [{
|
|
35
|
+
'group': 'yafowil.widget.autocomplete.common',
|
|
36
|
+
'resource': 'widget.js',
|
|
37
|
+
'order': 21,
|
|
38
|
+
}]
|
|
39
|
+
css = [{
|
|
40
|
+
'group': 'yafowil.widget.autocomplete.common',
|
|
41
|
+
'resource': 'widget.css',
|
|
42
|
+
'order': 21,
|
|
43
|
+
}]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
##############################################################################
|
|
47
|
+
# Registration
|
|
48
|
+
##############################################################################
|
|
49
|
+
|
|
50
|
+
@entry_point(order=10)
|
|
51
|
+
def register():
|
|
52
|
+
from yafowil.widget.autocomplete import widget # noqa
|
|
53
|
+
|
|
54
|
+
widget_name = 'yafowil.widget.autocomplete'
|
|
55
|
+
|
|
56
|
+
# Default
|
|
57
|
+
factory.register_theme(
|
|
58
|
+
'default',
|
|
59
|
+
widget_name,
|
|
60
|
+
resources_dir,
|
|
61
|
+
js=js,
|
|
62
|
+
css=css
|
|
63
|
+
)
|
|
64
|
+
factory.register_resources('default', widget_name, resources)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from yafowil.base import factory
|
|
2
|
+
from yafowil.compat import IS_PY2
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
if IS_PY2:
|
|
7
|
+
from urlparse import urlparse
|
|
8
|
+
from urlparse import parse_qs
|
|
9
|
+
else:
|
|
10
|
+
from urllib.parse import urlparse
|
|
11
|
+
from urllib.parse import parse_qs
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
lipsum = """Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
|
|
15
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
|
16
|
+
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
|
17
|
+
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
|
18
|
+
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
|
|
19
|
+
sunt in culpa qui officia deserunt mollit anim id est laborum."""
|
|
20
|
+
lipsum = sorted(set(lipsum.lower().replace('.', '').replace(',', '').split()))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def json_response(url):
|
|
24
|
+
purl = urlparse(url)
|
|
25
|
+
qs = parse_qs(purl.query)
|
|
26
|
+
data = json_data(qs.get('term', [''])[0])
|
|
27
|
+
return {
|
|
28
|
+
'body': json.dumps(data),
|
|
29
|
+
'header': [('Content-Type', 'application/json')]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def json_data(term):
|
|
34
|
+
data = lipsum
|
|
35
|
+
if term:
|
|
36
|
+
data = [_ for _ in data if _.startswith(term)]
|
|
37
|
+
return data
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
DOC_STATIC = """\
|
|
41
|
+
Autocomplete with static vocabulary
|
|
42
|
+
-----------------------------------
|
|
43
|
+
|
|
44
|
+
Autocomplete can make use of a static vocabulary source. Here words of blind
|
|
45
|
+
text Lorem Ipsum are used. First the static list of words is generated
|
|
46
|
+
|
|
47
|
+
.. code-block:: python
|
|
48
|
+
|
|
49
|
+
lipsum = '''Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
|
|
50
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
|
51
|
+
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
|
52
|
+
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit
|
|
53
|
+
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
|
|
54
|
+
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
|
|
55
|
+
est laborum.'''
|
|
56
|
+
lipsum = sorted(set(lipsum.lower().replace('.', '').replace(',', '').split()))
|
|
57
|
+
|
|
58
|
+
Next, the field is defined as
|
|
59
|
+
|
|
60
|
+
.. code-block:: python
|
|
61
|
+
|
|
62
|
+
field = factory('#field:autocomplete', props={
|
|
63
|
+
'label': 'Enter some text (local, lorem ipsum)',
|
|
64
|
+
'value': '',
|
|
65
|
+
'source': lipsum,
|
|
66
|
+
})
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
DOC_JSON = """\
|
|
70
|
+
Autocomplete with dynamic json vocabulary
|
|
71
|
+
-----------------------------------------
|
|
72
|
+
|
|
73
|
+
Instead of static lists autocomplete may ask the server for a list of words
|
|
74
|
+
matching a given term. The source is a string and as such interpreted as a
|
|
75
|
+
absolute or relative url
|
|
76
|
+
|
|
77
|
+
.. code-block:: python
|
|
78
|
+
|
|
79
|
+
field = factory('#field:autocomplete', props={
|
|
80
|
+
'label': 'Enter some text (remote listdir)',
|
|
81
|
+
'value': '',
|
|
82
|
+
'source': 'yafowil.widget.autocomplete.json',
|
|
83
|
+
'minLength': 1,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
The server answers with a JSON response, here the example does it using WSGI
|
|
87
|
+
and ``webob`` way. This code needs modification depending on the framework
|
|
88
|
+
used
|
|
89
|
+
|
|
90
|
+
.. code-block:: python
|
|
91
|
+
|
|
92
|
+
def json_response(environ, start_response):
|
|
93
|
+
data = lipsum
|
|
94
|
+
if environ['QUERY_STRING'].startswith('term='):
|
|
95
|
+
qsts = environ['QUERY_STRING'][5:]
|
|
96
|
+
data = [_ for _ in data if _.startswith(qsts)]
|
|
97
|
+
response = Response(content_type='application/json',
|
|
98
|
+
body=json.dumps(data))
|
|
99
|
+
return response(environ, start_response)
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_example():
|
|
104
|
+
static_ac = factory('#field:autocomplete', name='static', props={
|
|
105
|
+
'label': 'Enter some text (local, lorem ipsum)',
|
|
106
|
+
'value': '',
|
|
107
|
+
'source': lipsum,
|
|
108
|
+
})
|
|
109
|
+
json_ac = factory('#field:autocomplete', name='json', props={
|
|
110
|
+
'label': 'Enter some text (remote listdir)',
|
|
111
|
+
'value': '',
|
|
112
|
+
'source': 'yafowil.widget.autocomplete.json',
|
|
113
|
+
'minLength': 1,
|
|
114
|
+
})
|
|
115
|
+
routes = {
|
|
116
|
+
'yafowil.widget.autocomplete.json': json_response,
|
|
117
|
+
}
|
|
118
|
+
return [{
|
|
119
|
+
'widget': static_ac,
|
|
120
|
+
'doc': DOC_STATIC,
|
|
121
|
+
'title': 'Static autocomplete',
|
|
122
|
+
}, {
|
|
123
|
+
'widget': json_ac,
|
|
124
|
+
'routes': routes,
|
|
125
|
+
'doc': DOC_JSON,
|
|
126
|
+
'title': 'JSON data autocomplete',
|
|
127
|
+
}]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
div.yafowil-widget-autocomplete .hiddenStructure {
|
|
2
|
+
display: none;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
div.autocomplete-dropdown {
|
|
6
|
+
display: none;
|
|
7
|
+
position: absolute;
|
|
8
|
+
z-index: 2000;
|
|
9
|
+
min-width: 200px;
|
|
10
|
+
max-height: 320px;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
overflow-y: auto;
|
|
13
|
+
background: white;
|
|
14
|
+
border: 1px solid rgb(0 0 0 / 20%);
|
|
15
|
+
border-radius: 4px;
|
|
16
|
+
padding: 0;
|
|
17
|
+
box-shadow: 1px 1px 4px rgb(0 0 0 / 20%);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.autocomplete-suggestion {
|
|
21
|
+
display: block;
|
|
22
|
+
cursor: default;
|
|
23
|
+
padding: 5px 15px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.autocomplete-suggestion.selected {
|
|
27
|
+
background: var(--yafowil-accent-color, #0d6efd);
|
|
28
|
+
color: var(--yafowil-accent-font-color, #fff);
|
|
29
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
var yafowil_autocomplete = (function (exports, $) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
class AutocompleteSuggestion {
|
|
5
|
+
constructor(widget, source, val) {
|
|
6
|
+
this.widget = widget;
|
|
7
|
+
this.elem = $('<div />')
|
|
8
|
+
.addClass('autocomplete-suggestion')
|
|
9
|
+
.appendTo(this.widget.dd_elem);
|
|
10
|
+
let index = source.toUpperCase().indexOf(val.toUpperCase());
|
|
11
|
+
$(`<span />`)
|
|
12
|
+
.text(source.substring(0, index))
|
|
13
|
+
.appendTo(this.elem);
|
|
14
|
+
$(`<strong />`)
|
|
15
|
+
.text(source.substring(index, index + val.length))
|
|
16
|
+
.appendTo(this.elem);
|
|
17
|
+
$(`<span />`)
|
|
18
|
+
.text(source.substring(index + val.length))
|
|
19
|
+
.appendTo(this.elem);
|
|
20
|
+
this.value = source;
|
|
21
|
+
this.selected = false;
|
|
22
|
+
this.select = this.select.bind(this);
|
|
23
|
+
this.elem.off('mousedown', this.select).on('mousedown', this.select);
|
|
24
|
+
}
|
|
25
|
+
get selected() {
|
|
26
|
+
return this._selected;
|
|
27
|
+
}
|
|
28
|
+
set selected(selected) {
|
|
29
|
+
if (selected) {
|
|
30
|
+
this._selected = true;
|
|
31
|
+
this.elem.addClass('selected');
|
|
32
|
+
} else {
|
|
33
|
+
this._selected = false;
|
|
34
|
+
this.elem.removeClass('selected');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
select() {
|
|
38
|
+
this.selected = true;
|
|
39
|
+
this.widget.select_suggestion(this.value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
class AutocompleteWidget {
|
|
43
|
+
static initialize(context) {
|
|
44
|
+
$('div.yafowil-widget-autocomplete', context).each(function() {
|
|
45
|
+
let elem = $(this);
|
|
46
|
+
if (window.yafowil_array !== undefined &&
|
|
47
|
+
window.yafowil_array.inside_template(elem)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
new AutocompleteWidget(elem);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
constructor(elem) {
|
|
54
|
+
elem.data('yafowil-autocomplete', this);
|
|
55
|
+
this.elem = elem;
|
|
56
|
+
this.input_elem = $('input.autocomplete', this.elem)
|
|
57
|
+
.attr('spellcheck', false)
|
|
58
|
+
.attr('autocomplete', 'off');
|
|
59
|
+
this.dd_elem = $(`<div />`)
|
|
60
|
+
.addClass('autocomplete-dropdown')
|
|
61
|
+
.appendTo('body');
|
|
62
|
+
this.suggestions = [];
|
|
63
|
+
this.current_focus = 0;
|
|
64
|
+
let options = this.parse_options();
|
|
65
|
+
this.sourcetype = options.type;
|
|
66
|
+
this.delay = options.delay;
|
|
67
|
+
this.min_length = options.minLength;
|
|
68
|
+
this.parse_source();
|
|
69
|
+
this.on_input = this.on_input.bind(this);
|
|
70
|
+
this.hide_dropdown = this.hide_dropdown.bind(this);
|
|
71
|
+
this.on_keydown = this.on_keydown.bind(this);
|
|
72
|
+
this.input_elem
|
|
73
|
+
.on('focusout', this.hide_dropdown)
|
|
74
|
+
.on('focus input', this.on_input)
|
|
75
|
+
.on('keydown', this.on_keydown);
|
|
76
|
+
this.autocomplete = this.autocomplete.bind(this);
|
|
77
|
+
}
|
|
78
|
+
unload() {
|
|
79
|
+
clearTimeout(this.timeout);
|
|
80
|
+
this.input_elem
|
|
81
|
+
.off('focusout', this.hide_dropdown)
|
|
82
|
+
.off('focus input', this.on_input)
|
|
83
|
+
.off('keydown', this.on_keydown);
|
|
84
|
+
}
|
|
85
|
+
parse_options() {
|
|
86
|
+
let rawparams = $('.autocomplete-params', this.elem).text().split('|'),
|
|
87
|
+
options = [];
|
|
88
|
+
for (let i = 0; i < rawparams.length; i++) {
|
|
89
|
+
let pair = rawparams[i].split(',');
|
|
90
|
+
let value = pair[1].replace(/^\s+|\s+$/g, "");
|
|
91
|
+
if (!isNaN(value)) {
|
|
92
|
+
value = parseInt(value);
|
|
93
|
+
} else
|
|
94
|
+
if (value === 'True') {
|
|
95
|
+
value = true;
|
|
96
|
+
} else
|
|
97
|
+
if (value === 'False') {
|
|
98
|
+
value = false;
|
|
99
|
+
}
|
|
100
|
+
let key = pair[0].replace(/^\s+|\s+$/g, "");
|
|
101
|
+
options[key] = value;
|
|
102
|
+
}
|
|
103
|
+
return options;
|
|
104
|
+
}
|
|
105
|
+
parse_source() {
|
|
106
|
+
let source = $('.autocomplete-source', this.elem).text();
|
|
107
|
+
if (source.indexOf('javascript:') === 0) {
|
|
108
|
+
let src = source.substring(11, source.length).split('.');
|
|
109
|
+
let window_src = window;
|
|
110
|
+
for (let part of src) {
|
|
111
|
+
window_src = window_src[part];
|
|
112
|
+
if (window_src === undefined) {
|
|
113
|
+
throw new Error('Cannot locate source function: ' + source);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
this.source = function(request, response) {
|
|
117
|
+
window_src(request, response);
|
|
118
|
+
};
|
|
119
|
+
} else if (this.sourcetype === 'local') {
|
|
120
|
+
if (source === '') {
|
|
121
|
+
throw new Error('Local source is empty');
|
|
122
|
+
}
|
|
123
|
+
this.source = function(request, response) {
|
|
124
|
+
let src = source.split('|'),
|
|
125
|
+
term = request.term,
|
|
126
|
+
data = [];
|
|
127
|
+
for (let item of src) {
|
|
128
|
+
if (item.toUpperCase().indexOf(term.toUpperCase()) > -1) {
|
|
129
|
+
data.push(item);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
response(data);
|
|
133
|
+
};
|
|
134
|
+
} else if (this.sourcetype === 'remote') {
|
|
135
|
+
this.source = function(request, response) {
|
|
136
|
+
$.ajax({
|
|
137
|
+
url: source,
|
|
138
|
+
data: {term: request.term},
|
|
139
|
+
dataType: "json",
|
|
140
|
+
success: function(data) {
|
|
141
|
+
response(data);
|
|
142
|
+
},
|
|
143
|
+
error: function() {
|
|
144
|
+
response([]);
|
|
145
|
+
throw new Error('Cannot locate JSON at: ' + source);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
on_input(e) {
|
|
152
|
+
clearTimeout(this.timeout);
|
|
153
|
+
this.dd_elem.empty().hide();
|
|
154
|
+
this.suggestions = [];
|
|
155
|
+
this.current_focus = -1;
|
|
156
|
+
if (this.input_elem.val().length >= this.min_length) {
|
|
157
|
+
this.timeout = setTimeout(this.autocomplete, this.delay);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
autocomplete() {
|
|
161
|
+
let val = this.input_elem.val();
|
|
162
|
+
this.source({term: val}, (data) => {
|
|
163
|
+
if(!data.length) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
for (let item of data) {
|
|
167
|
+
this.suggestions.push(new AutocompleteSuggestion(this, item, val));
|
|
168
|
+
}
|
|
169
|
+
let scrolltop = $(document).scrollTop(),
|
|
170
|
+
input_top = this.elem.offset().top,
|
|
171
|
+
input_left = this.elem.offset().left,
|
|
172
|
+
input_height = this.elem.outerHeight(),
|
|
173
|
+
dd_height = this.dd_elem.outerHeight(),
|
|
174
|
+
top;
|
|
175
|
+
let viewport_edge = scrolltop + $(window).outerHeight();
|
|
176
|
+
let dd_bottom = input_top + input_height + dd_height;
|
|
177
|
+
if (dd_bottom >= viewport_edge) {
|
|
178
|
+
top = input_top - dd_height;
|
|
179
|
+
} else {
|
|
180
|
+
top = input_top + input_height;
|
|
181
|
+
}
|
|
182
|
+
this.dd_elem.css({
|
|
183
|
+
top: `${top}px`,
|
|
184
|
+
left: `${input_left}px`
|
|
185
|
+
});
|
|
186
|
+
this.dd_elem.show();
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
on_keydown(e) {
|
|
190
|
+
let scrolltop = this.dd_elem.scrollTop();
|
|
191
|
+
switch (e.key) {
|
|
192
|
+
case "ArrowDown":
|
|
193
|
+
this.current_focus++;
|
|
194
|
+
this.add_active(true);
|
|
195
|
+
break;
|
|
196
|
+
case "ArrowUp":
|
|
197
|
+
this.current_focus--;
|
|
198
|
+
this.add_active(false);
|
|
199
|
+
break;
|
|
200
|
+
case "Enter":
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
if (this.current_focus > -1) {
|
|
203
|
+
let selected_elem = this.suggestions[this.current_focus];
|
|
204
|
+
selected_elem.selected = true;
|
|
205
|
+
this.input_elem.val(selected_elem.value);
|
|
206
|
+
this.hide_dropdown();
|
|
207
|
+
this.input_elem.trigger('blur');
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
case "Escape":
|
|
211
|
+
this.hide_dropdown();
|
|
212
|
+
this.input_elem.trigger('blur');
|
|
213
|
+
break;
|
|
214
|
+
case "Tab":
|
|
215
|
+
this.hide_dropdown();
|
|
216
|
+
if (this.current_focus > -1) {
|
|
217
|
+
let selected_elem = this.suggestions[this.current_focus];
|
|
218
|
+
this.input_elem.val(selected_elem.value);
|
|
219
|
+
this.hide_dropdown();
|
|
220
|
+
this.input_elem.trigger('blur');
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
case "PageDown":
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
this.dd_elem.scrollTop(scrolltop + this.dd_elem.height());
|
|
226
|
+
if (this.current_focus > -1) {
|
|
227
|
+
let index = 0;
|
|
228
|
+
for (let i in this.suggestions) {
|
|
229
|
+
let elem = this.suggestions[i].elem;
|
|
230
|
+
if (elem.offset().top < this.dd_elem.offset().top) {
|
|
231
|
+
index++;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
this.current_focus = index;
|
|
235
|
+
let selected_elem = this.suggestions[index];
|
|
236
|
+
this.unselect_all();
|
|
237
|
+
selected_elem.selected = true;
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
case "PageUp":
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
this.dd_elem.scrollTop(scrolltop - this.dd_elem.height());
|
|
243
|
+
if (this.current_focus > -1) {
|
|
244
|
+
let index = 0;
|
|
245
|
+
for (let i in this.suggestions) {
|
|
246
|
+
let elem = this.suggestions[i].elem;
|
|
247
|
+
if (elem.offset().top < this.dd_elem.offset().top) {
|
|
248
|
+
index++;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
this.current_focus = index;
|
|
252
|
+
let selected_elem = this.suggestions[index];
|
|
253
|
+
this.unselect_all();
|
|
254
|
+
selected_elem.selected = true;
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
select_suggestion(val) {
|
|
260
|
+
this.hide_dropdown();
|
|
261
|
+
this.input_elem.val(val);
|
|
262
|
+
}
|
|
263
|
+
unselect_all() {
|
|
264
|
+
for (let suggestion of this.suggestions) {
|
|
265
|
+
suggestion.selected = false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
add_active(dir) {
|
|
269
|
+
if (this.suggestions.length === 0) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
this.unselect_all();
|
|
273
|
+
if (this.current_focus >= this.suggestions.length) {
|
|
274
|
+
this.current_focus = 0;
|
|
275
|
+
} else if (this.current_focus < 0) {
|
|
276
|
+
this.current_focus = (this.suggestions.length - 1);
|
|
277
|
+
}
|
|
278
|
+
let active_elem = this.suggestions[this.current_focus];
|
|
279
|
+
active_elem.selected = true;
|
|
280
|
+
let scrolltop = this.dd_elem.scrollTop();
|
|
281
|
+
let elem_top = active_elem.elem.offset().top;
|
|
282
|
+
let elem_height = active_elem.elem.outerHeight();
|
|
283
|
+
let dd_top = this.dd_elem.offset().top;
|
|
284
|
+
let dd_height = this.dd_elem.outerHeight();
|
|
285
|
+
if (dir) {
|
|
286
|
+
if (this.current_focus === 0) {
|
|
287
|
+
this.dd_elem.scrollTop(0);
|
|
288
|
+
} else if (elem_top + elem_height > dd_top + dd_height) {
|
|
289
|
+
this.dd_elem.scrollTop(scrolltop + elem_height);
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
if (this.current_focus >= this.suggestions.length - 1) {
|
|
293
|
+
this.dd_elem.scrollTop(elem_height * this.suggestions.length);
|
|
294
|
+
} else if (elem_top < dd_top) {
|
|
295
|
+
this.dd_elem.scrollTop(scrolltop - elem_height);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
hide_dropdown() {
|
|
300
|
+
this.dd_elem.hide();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function autocomplete_on_array_add(inst, context) {
|
|
304
|
+
AutocompleteWidget.initialize(context);
|
|
305
|
+
}
|
|
306
|
+
function register_array_subscribers() {
|
|
307
|
+
if (window.yafowil_array !== undefined) {
|
|
308
|
+
window.yafowil_array.on_array_event('on_add', autocomplete_on_array_add);
|
|
309
|
+
} else if (yafowil.array !== undefined) {
|
|
310
|
+
$.extend(yafowil.array.hooks.add, {
|
|
311
|
+
autocomplete_binder: AutocompleteWidget.initialize
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
$(function() {
|
|
317
|
+
if (window.ts !== undefined) {
|
|
318
|
+
ts.ajax.register(AutocompleteWidget.initialize, true);
|
|
319
|
+
} else if (window.bdajax !== undefined) {
|
|
320
|
+
bdajax.register(AutocompleteWidget.initialize, true);
|
|
321
|
+
} else {
|
|
322
|
+
AutocompleteWidget.initialize();
|
|
323
|
+
}
|
|
324
|
+
register_array_subscribers();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
exports.AutocompleteSuggestion = AutocompleteSuggestion;
|
|
328
|
+
exports.AutocompleteWidget = AutocompleteWidget;
|
|
329
|
+
exports.register_array_subscribers = register_array_subscribers;
|
|
330
|
+
|
|
331
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
window.yafowil = window.yafowil || {};
|
|
335
|
+
window.yafowil.autocomplete = exports;
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
return exports;
|
|
339
|
+
|
|
340
|
+
})({}, jQuery);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var yafowil_autocomplete=function(e,t){"use strict";class s{constructor(e,s,i){this.widget=e,this.elem=t("<div />").addClass("autocomplete-suggestion").appendTo(this.widget.dd_elem);let o=s.toUpperCase().indexOf(i.toUpperCase());t("<span />").text(s.substring(0,o)).appendTo(this.elem),t("<strong />").text(s.substring(o,o+i.length)).appendTo(this.elem),t("<span />").text(s.substring(o+i.length)).appendTo(this.elem),this.value=s,this.selected=!1,this.select=this.select.bind(this),this.elem.off("mousedown",this.select).on("mousedown",this.select)}get selected(){return this._selected}set selected(e){e?(this._selected=!0,this.elem.addClass("selected")):(this._selected=!1,this.elem.removeClass("selected"))}select(){this.selected=!0,this.widget.select_suggestion(this.value)}}class i{static initialize(e){t("div.yafowil-widget-autocomplete",e).each((function(){let e=t(this);void 0!==window.yafowil_array&&window.yafowil_array.inside_template(e)||new i(e)}))}constructor(e){e.data("yafowil-autocomplete",this),this.elem=e,this.input_elem=t("input.autocomplete",this.elem).attr("spellcheck",!1).attr("autocomplete","off"),this.dd_elem=t("<div />").addClass("autocomplete-dropdown").appendTo("body"),this.suggestions=[],this.current_focus=0;let s=this.parse_options();this.sourcetype=s.type,this.delay=s.delay,this.min_length=s.minLength,this.parse_source(),this.on_input=this.on_input.bind(this),this.hide_dropdown=this.hide_dropdown.bind(this),this.on_keydown=this.on_keydown.bind(this),this.input_elem.on("focusout",this.hide_dropdown).on("focus input",this.on_input).on("keydown",this.on_keydown),this.autocomplete=this.autocomplete.bind(this)}unload(){clearTimeout(this.timeout),this.input_elem.off("focusout",this.hide_dropdown).off("focus input",this.on_input).off("keydown",this.on_keydown)}parse_options(){let e=t(".autocomplete-params",this.elem).text().split("|"),s=[];for(let t=0;t<e.length;t++){let i=e[t].split(","),o=i[1].replace(/^\s+|\s+$/g,"");isNaN(o)?"True"===o?o=!0:"False"===o&&(o=!1):o=parseInt(o),s[i[0].replace(/^\s+|\s+$/g,"")]=o}return s}parse_source(){let e=t(".autocomplete-source",this.elem).text();if(0===e.indexOf("javascript:")){let t=e.substring(11,e.length).split("."),s=window;for(let i of t)if(s=s[i],void 0===s)throw new Error("Cannot locate source function: "+e);this.source=function(e,t){s(e,t)}}else if("local"===this.sourcetype){if(""===e)throw new Error("Local source is empty");this.source=function(t,s){let i=e.split("|"),o=t.term,l=[];for(let e of i)e.toUpperCase().indexOf(o.toUpperCase())>-1&&l.push(e);s(l)}}else"remote"===this.sourcetype&&(this.source=function(s,i){t.ajax({url:e,data:{term:s.term},dataType:"json",success:function(e){i(e)},error:function(){throw i([]),new Error("Cannot locate JSON at: "+e)}})})}on_input(e){clearTimeout(this.timeout),this.dd_elem.empty().hide(),this.suggestions=[],this.current_focus=-1,this.input_elem.val().length>=this.min_length&&(this.timeout=setTimeout(this.autocomplete,this.delay))}autocomplete(){let e=this.input_elem.val();this.source({term:e},(i=>{if(!i.length)return;for(let t of i)this.suggestions.push(new s(this,t,e));let o,l=t(document).scrollTop(),n=this.elem.offset().top,r=this.elem.offset().left,h=this.elem.outerHeight(),u=this.dd_elem.outerHeight();o=n+h+u>=l+t(window).outerHeight()?n-u:n+h,this.dd_elem.css({top:`${o}px`,left:`${r}px`}),this.dd_elem.show()}))}on_keydown(e){let t=this.dd_elem.scrollTop();switch(e.key){case"ArrowDown":this.current_focus++,this.add_active(!0);break;case"ArrowUp":this.current_focus--,this.add_active(!1);break;case"Enter":if(e.preventDefault(),this.current_focus>-1){let e=this.suggestions[this.current_focus];e.selected=!0,this.input_elem.val(e.value),this.hide_dropdown(),this.input_elem.trigger("blur")}break;case"Escape":this.hide_dropdown(),this.input_elem.trigger("blur");break;case"Tab":if(this.hide_dropdown(),this.current_focus>-1){let e=this.suggestions[this.current_focus];this.input_elem.val(e.value),this.hide_dropdown(),this.input_elem.trigger("blur")}break;case"PageDown":if(e.preventDefault(),this.dd_elem.scrollTop(t+this.dd_elem.height()),this.current_focus>-1){let e=0;for(let t in this.suggestions){this.suggestions[t].elem.offset().top<this.dd_elem.offset().top&&e++}this.current_focus=e;let t=this.suggestions[e];this.unselect_all(),t.selected=!0}break;case"PageUp":if(e.preventDefault(),this.dd_elem.scrollTop(t-this.dd_elem.height()),this.current_focus>-1){let e=0;for(let t in this.suggestions){this.suggestions[t].elem.offset().top<this.dd_elem.offset().top&&e++}this.current_focus=e;let t=this.suggestions[e];this.unselect_all(),t.selected=!0}}}select_suggestion(e){this.hide_dropdown(),this.input_elem.val(e)}unselect_all(){for(let e of this.suggestions)e.selected=!1}add_active(e){if(0===this.suggestions.length)return;this.unselect_all(),this.current_focus>=this.suggestions.length?this.current_focus=0:this.current_focus<0&&(this.current_focus=this.suggestions.length-1);let t=this.suggestions[this.current_focus];t.selected=!0;let s=this.dd_elem.scrollTop(),i=t.elem.offset().top,o=t.elem.outerHeight(),l=this.dd_elem.offset().top,n=this.dd_elem.outerHeight();e?0===this.current_focus?this.dd_elem.scrollTop(0):i+o>l+n&&this.dd_elem.scrollTop(s+o):this.current_focus>=this.suggestions.length-1?this.dd_elem.scrollTop(o*this.suggestions.length):i<l&&this.dd_elem.scrollTop(s-o)}hide_dropdown(){this.dd_elem.hide()}}function o(e,t){i.initialize(t)}function l(){void 0!==window.yafowil_array?window.yafowil_array.on_array_event("on_add",o):void 0!==yafowil.array&&t.extend(yafowil.array.hooks.add,{autocomplete_binder:i.initialize})}return t((function(){void 0!==window.ts?ts.ajax.register(i.initialize,!0):void 0!==window.bdajax?bdajax.register(i.initialize,!0):i.initialize(),l()})),e.AutocompleteSuggestion=s,e.AutocompleteWidget=i,e.register_array_subscribers=l,Object.defineProperty(e,"__esModule",{value:!0}),window.yafowil=window.yafowil||{},window.yafowil.autocomplete=e,e}({},jQuery);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from node.utils import UNSET
|
|
2
|
+
from yafowil.base import ExtractionError
|
|
3
|
+
from yafowil.base import factory
|
|
4
|
+
from yafowil.compat import IS_PY2
|
|
5
|
+
from yafowil.tests import fxml
|
|
6
|
+
from yafowil.tests import YafowilTestCase
|
|
7
|
+
import os
|
|
8
|
+
import unittest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if not IS_PY2:
|
|
12
|
+
from importlib import reload
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def np(path):
|
|
16
|
+
return path.replace('/', os.path.sep)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestAutocompleteWidget(YafowilTestCase):
|
|
20
|
+
|
|
21
|
+
def setUp(self):
|
|
22
|
+
super(TestAutocompleteWidget, self).setUp()
|
|
23
|
+
from yafowil.widget import autocomplete
|
|
24
|
+
from yafowil.widget.autocomplete import widget
|
|
25
|
+
reload(widget)
|
|
26
|
+
autocomplete.register()
|
|
27
|
+
|
|
28
|
+
def test_source_is_string(self):
|
|
29
|
+
# Render plain, source is string
|
|
30
|
+
widget = factory(
|
|
31
|
+
'autocomplete',
|
|
32
|
+
name='root',
|
|
33
|
+
props={
|
|
34
|
+
'source': 'http://www.foo.bar/baz'
|
|
35
|
+
})
|
|
36
|
+
self.checkOutput("""
|
|
37
|
+
<div class="yafowil-widget-autocomplete">
|
|
38
|
+
<input class="autocomplete" id="input-root" name="root" type="text"/>
|
|
39
|
+
<div class="autocomplete-source hiddenStructure">http://www.foo.bar/baz</div>
|
|
40
|
+
<div class="autocomplete-params hiddenStructure">delay,300|minLength,1|type,remote</div>
|
|
41
|
+
</div>
|
|
42
|
+
""", fxml(widget()))
|
|
43
|
+
|
|
44
|
+
def test_source_is_list(self):
|
|
45
|
+
# Render plain, source is list
|
|
46
|
+
widget = factory(
|
|
47
|
+
'autocomplete',
|
|
48
|
+
name='root',
|
|
49
|
+
props={
|
|
50
|
+
'source': ['foo', 'bar']
|
|
51
|
+
})
|
|
52
|
+
self.checkOutput("""
|
|
53
|
+
<div class="yafowil-widget-autocomplete">
|
|
54
|
+
<input class="autocomplete" id="input-root" name="root" type="text"/>
|
|
55
|
+
<div class="autocomplete-source hiddenStructure">foo|bar</div>
|
|
56
|
+
<div class="autocomplete-params hiddenStructure">delay,300|minLength,1|type,local</div>
|
|
57
|
+
</div>
|
|
58
|
+
""", fxml(widget()))
|
|
59
|
+
|
|
60
|
+
def test_source_is_callable(self):
|
|
61
|
+
# Render plain, source is callable
|
|
62
|
+
def test_source(widget, data):
|
|
63
|
+
return 'http://from.callable/'
|
|
64
|
+
|
|
65
|
+
widget = factory(
|
|
66
|
+
'autocomplete',
|
|
67
|
+
name='root',
|
|
68
|
+
props={
|
|
69
|
+
'source': test_source
|
|
70
|
+
})
|
|
71
|
+
self.checkOutput("""
|
|
72
|
+
<div class="yafowil-widget-autocomplete">
|
|
73
|
+
<input class="autocomplete" id="input-root" name="root" type="text"/>
|
|
74
|
+
<div class="autocomplete-source hiddenStructure">http://from.callable/</div>
|
|
75
|
+
<div class="autocomplete-params hiddenStructure">delay,300|minLength,1|type,remote</div>
|
|
76
|
+
</div>""", fxml(widget()))
|
|
77
|
+
|
|
78
|
+
def test_extraction(self):
|
|
79
|
+
def test_source(widget, data):
|
|
80
|
+
return 'http://from.callable/'
|
|
81
|
+
|
|
82
|
+
widget = factory(
|
|
83
|
+
'autocomplete',
|
|
84
|
+
name='root',
|
|
85
|
+
props={
|
|
86
|
+
'source': test_source
|
|
87
|
+
})
|
|
88
|
+
data = widget.extract({'root': 'abc'})
|
|
89
|
+
self.assertEqual(
|
|
90
|
+
[data.name, data.value, data.extracted, data.errors],
|
|
91
|
+
['root', UNSET, 'abc', []]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
widget = factory(
|
|
95
|
+
'error:autocomplete',
|
|
96
|
+
name='root',
|
|
97
|
+
props={
|
|
98
|
+
'source': test_source,
|
|
99
|
+
'required': 'Autocomplete widget is required'
|
|
100
|
+
})
|
|
101
|
+
data = widget.extract({'root': ''})
|
|
102
|
+
error = ExtractionError('Autocomplete widget is required')
|
|
103
|
+
self.assertEqual(
|
|
104
|
+
[data.name, data.value, data.extracted, data.errors],
|
|
105
|
+
['root', UNSET, '', [error]]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
self.checkOutput("""
|
|
109
|
+
<div class="error">
|
|
110
|
+
<div class="errormessage">Autocomplete widget is required</div>
|
|
111
|
+
<div class="yafowil-widget-autocomplete">
|
|
112
|
+
<input class="autocomplete required" id="input-root" name="root"
|
|
113
|
+
required="required" type="text" value=""/>
|
|
114
|
+
<div class="autocomplete-source hiddenStructure">http://from.callable/</div>
|
|
115
|
+
<div class="autocomplete-params hiddenStructure">delay,300|minLength,1|type,remote</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
""", fxml(widget(data)))
|
|
119
|
+
|
|
120
|
+
def test_invalid_source_type(self):
|
|
121
|
+
widget = factory(
|
|
122
|
+
'autocomplete',
|
|
123
|
+
name='root',
|
|
124
|
+
props={
|
|
125
|
+
'source': None
|
|
126
|
+
})
|
|
127
|
+
with self.assertRaises(ValueError) as arc:
|
|
128
|
+
widget()
|
|
129
|
+
self.assertEqual(
|
|
130
|
+
str(arc.exception),
|
|
131
|
+
'resulting source must be tuple/list or string'
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def test_resources(self):
|
|
135
|
+
factory.theme = 'default'
|
|
136
|
+
resources = factory.get_resources('yafowil.widget.autocomplete')
|
|
137
|
+
self.assertTrue(
|
|
138
|
+
resources.directory.endswith(np('/autocomplete/resources'))
|
|
139
|
+
)
|
|
140
|
+
self.assertEqual(resources.name, 'yafowil.widget.autocomplete')
|
|
141
|
+
self.assertEqual(resources.path, 'yafowil-autocomplete')
|
|
142
|
+
|
|
143
|
+
scripts = resources.scripts
|
|
144
|
+
self.assertEqual(len(scripts), 1)
|
|
145
|
+
|
|
146
|
+
self.assertTrue(
|
|
147
|
+
scripts[0].directory.endswith(np('/autocomplete/resources'))
|
|
148
|
+
)
|
|
149
|
+
self.assertEqual(scripts[0].path, 'yafowil-autocomplete')
|
|
150
|
+
self.assertEqual(scripts[0].file_name, 'widget.min.js')
|
|
151
|
+
self.assertTrue(os.path.exists(scripts[0].file_path))
|
|
152
|
+
|
|
153
|
+
styles = resources.styles
|
|
154
|
+
self.assertEqual(len(styles), 1)
|
|
155
|
+
|
|
156
|
+
self.assertTrue(
|
|
157
|
+
styles[0].directory.endswith(np('/autocomplete/resources'))
|
|
158
|
+
)
|
|
159
|
+
self.assertEqual(styles[0].path, 'yafowil-autocomplete')
|
|
160
|
+
self.assertEqual(styles[0].file_name, 'widget.css')
|
|
161
|
+
self.assertTrue(os.path.exists(styles[0].file_path))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == '__main__':
|
|
165
|
+
unittest.main()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from yafowil.base import factory
|
|
2
|
+
from yafowil.common import generic_extractor
|
|
3
|
+
from yafowil.common import generic_required_extractor
|
|
4
|
+
from yafowil.common import input_generic_renderer
|
|
5
|
+
from yafowil.compat import STR_TYPE
|
|
6
|
+
from yafowil.utils import attr_value
|
|
7
|
+
from yafowil.utils import managedprops
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@managedprops('source', 'delay', 'minLength')
|
|
11
|
+
def autocomplete_renderer(widget, data):
|
|
12
|
+
result = data.rendered
|
|
13
|
+
tag = data.tag
|
|
14
|
+
source = attr_value('source', widget, data)
|
|
15
|
+
if isinstance(source, (list, tuple)):
|
|
16
|
+
source = '|'.join(source)
|
|
17
|
+
source_type = 'local'
|
|
18
|
+
elif isinstance(source, STR_TYPE):
|
|
19
|
+
source_type = 'remote'
|
|
20
|
+
else:
|
|
21
|
+
raise ValueError('resulting source must be tuple/list or string')
|
|
22
|
+
result += tag('div', source, **{
|
|
23
|
+
'class': 'autocomplete-source hiddenStructure'
|
|
24
|
+
})
|
|
25
|
+
params = [
|
|
26
|
+
('%s,%s' % (_, attr_value(_, widget, data)))
|
|
27
|
+
for _ in ['delay', 'minLength']
|
|
28
|
+
]
|
|
29
|
+
params.append('type,%s' % source_type)
|
|
30
|
+
result += tag('div', '|'.join(params), **{
|
|
31
|
+
'class': 'autocomplete-params hiddenStructure'
|
|
32
|
+
})
|
|
33
|
+
return tag('div', result, **{'class': 'yafowil-widget-autocomplete'})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def autocomplete_extractor(widget, data):
|
|
37
|
+
return data.extracted
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
factory.register(
|
|
41
|
+
'autocomplete',
|
|
42
|
+
extractors=[
|
|
43
|
+
generic_extractor,
|
|
44
|
+
generic_required_extractor,
|
|
45
|
+
autocomplete_extractor
|
|
46
|
+
],
|
|
47
|
+
edit_renderers=[
|
|
48
|
+
input_generic_renderer,
|
|
49
|
+
autocomplete_renderer
|
|
50
|
+
]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
factory.doc['blueprint']['autocomplete'] = """\
|
|
54
|
+
Add-on blueprint `yafowil.widget.autocomplete
|
|
55
|
+
<http://github.com/conestack/yafowil.widget.autocomplete/>`_ utilizing
|
|
56
|
+
``jquery.ui.autocomplete`` to offer the user a selection based on the input
|
|
57
|
+
given so far.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
factory.defaults['autocomplete.type'] = 'text'
|
|
61
|
+
|
|
62
|
+
factory.defaults['autocomplete.class'] = 'autocomplete'
|
|
63
|
+
|
|
64
|
+
factory.defaults['autocomplete.required_class'] = 'required'
|
|
65
|
+
|
|
66
|
+
factory.defaults['autocomplete.disabled'] = False
|
|
67
|
+
|
|
68
|
+
factory.defaults['autocomplete.size'] = None
|
|
69
|
+
|
|
70
|
+
factory.defaults['autocomplete.delay'] = '300'
|
|
71
|
+
factory.doc['props']['autocomplete.delay'] = """\
|
|
72
|
+
Delay in milliseconds.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
factory.defaults['autocomplete.minLength'] = '1'
|
|
76
|
+
factory.doc['props']['autocomplete.minLength'] = """\
|
|
77
|
+
Minimum input length to trigger autocomplete.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
factory.doc['props']['autocomplete.source'] = """\
|
|
81
|
+
Autocomplete source as python iterable or string defining JSON view callback.
|
|
82
|
+
"""
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('yafowil',));importlib = __import__('importlib.util');__import__('importlib.machinery');m = sys.modules.setdefault('yafowil', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('yafowil', [os.path.dirname(p)])));m = m or sys.modules.setdefault('yafowil', types.ModuleType('yafowil'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
|
|
2
|
+
import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('yafowil', 'widget'));importlib = __import__('importlib.util');__import__('importlib.machinery');m = sys.modules.setdefault('yafowil.widget', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('yafowil.widget', [os.path.dirname(p)])));m = m or sys.modules.setdefault('yafowil.widget', types.ModuleType('yafowil.widget'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p);m and setattr(sys.modules['yafowil'], 'widget', m)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
License
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2010-2021, BlueDynamics Alliance, Austria, Germany, Switzerland
|
|
5
|
+
Copyright (c) 2021-2022, Yafowil Contributors
|
|
6
|
+
All rights reserved.
|
|
7
|
+
|
|
8
|
+
Redistribution and use in source and binary forms, with or without
|
|
9
|
+
modification, are permitted provided that the following conditions are met:
|
|
10
|
+
|
|
11
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
12
|
+
list of conditions and the following disclaimer.
|
|
13
|
+
|
|
14
|
+
* Redistributions in binary form must reproduce the above copyright notice, this
|
|
15
|
+
list of conditions and the following disclaimer in the documentation and/or
|
|
16
|
+
other materials provided with the distribution.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
25
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: yafowil.widget.autocomplete
|
|
3
|
+
Version: 2.0a1
|
|
4
|
+
Summary: Autocomplete Widget for YAFOWIL
|
|
5
|
+
Home-page: http://github.com/conestack/yafowil.widget.autocomplete
|
|
6
|
+
Author: Yafowil Contributors
|
|
7
|
+
Author-email: dev@conestack.org
|
|
8
|
+
License: Simplified BSD
|
|
9
|
+
Keywords: jquery jquery.ui.autocomplete widget yafowil
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Topic :: Software Development
|
|
13
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
14
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
15
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
+
License-File: LICENSE.rst
|
|
20
|
+
Requires-Dist: setuptools
|
|
21
|
+
Requires-Dist: yafowil >2.1.99
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: lxml ; extra == 'test'
|
|
24
|
+
Requires-Dist: zope.testrunner ; extra == 'test'
|
|
25
|
+
|
|
26
|
+
This is a **autocomplete widget** for for `YAFOWIL
|
|
27
|
+
<http://pypi.python.org/pypi/yafowil>`_ - Yet Another Form WIdget Library.
|
|
28
|
+
|
|
29
|
+
- `Documentation <http://docs.yafowil.info/en/latest/blueprints.html#autocomplete>`_
|
|
30
|
+
- `DEMO - see it Live <http://docs.demo.yafowil.info/++widget++yafowil.widget.autocomplete/index.html>`_
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Source Code
|
|
34
|
+
===========
|
|
35
|
+
|
|
36
|
+
The sources are in a GIT DVCS with its main branches at
|
|
37
|
+
`github <http://github.com/conestack/yafowil.widget.autocomplete>`_.
|
|
38
|
+
|
|
39
|
+
We'd be happy to see many forks and pull-requests to make YAFOWIL even better.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Contributors
|
|
43
|
+
============
|
|
44
|
+
|
|
45
|
+
- Jens Klein
|
|
46
|
+
|
|
47
|
+
- Robert Niederreiter
|
|
48
|
+
|
|
49
|
+
- Georg Bernhard
|
|
50
|
+
|
|
51
|
+
- Lena Daxenbichler
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
Changes
|
|
55
|
+
=======
|
|
56
|
+
|
|
57
|
+
2.0a1 (2024-06-02)
|
|
58
|
+
------------------
|
|
59
|
+
|
|
60
|
+
- Extend JS by ``autocomplete_on_array_add`` and ``register_array_subscribers``
|
|
61
|
+
to enable usage in ``yafowil.widget.array``.
|
|
62
|
+
[lenadax]
|
|
63
|
+
|
|
64
|
+
- Get rid of jQuery UI. Rewrite Jacascript widget using ES6.
|
|
65
|
+
[lenadax]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
1.7 (2018-07-16)
|
|
69
|
+
----------------
|
|
70
|
+
|
|
71
|
+
- Python 3 compatibility.
|
|
72
|
+
[rnix]
|
|
73
|
+
|
|
74
|
+
- Convert doctests to unittests.
|
|
75
|
+
[rnix]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
1.6 (2017-03-01)
|
|
79
|
+
----------------
|
|
80
|
+
|
|
81
|
+
- Add dedicated CSS for ``plone5`` theme provided by ``yafowil.plone``.
|
|
82
|
+
[rnix, 2016-06-28]
|
|
83
|
+
|
|
84
|
+
- Use ``yafowil.utils.entry_point`` decorator.
|
|
85
|
+
[rnix, 2016-06-28]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
1.5.1 (2015-06-25)
|
|
89
|
+
------------------
|
|
90
|
+
|
|
91
|
+
- Resolve JSHint errors and warnings.
|
|
92
|
+
[thet]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
1.5 (2015-01-23)
|
|
96
|
+
----------------
|
|
97
|
+
|
|
98
|
+
- Update jquery UI autocomplete to 1.10.3 and use latest jquery ui boostrap
|
|
99
|
+
styles.
|
|
100
|
+
[rnix, 2014-07-05]
|
|
101
|
+
|
|
102
|
+
1.4.1
|
|
103
|
+
-----
|
|
104
|
+
|
|
105
|
+
- use ``yafowil.utils.attr_value`` wherever possible.
|
|
106
|
+
[rnix, 2012-10-25]
|
|
107
|
+
|
|
108
|
+
1.4
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
- Add ``generic_required_extractor`` to extraction chain
|
|
112
|
+
[rnix, 2012-07-25]
|
|
113
|
+
|
|
114
|
+
- Adopt resource providing
|
|
115
|
+
[rnix, 2012-06-12]
|
|
116
|
+
|
|
117
|
+
- Remove example app
|
|
118
|
+
[rnix, 2012-06-12]
|
|
119
|
+
|
|
120
|
+
1.3
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
- Support JavaScript callback functions in source attribute
|
|
124
|
+
[rnix, 2011-10-03]
|
|
125
|
+
|
|
126
|
+
- register autocomplete binder in ``yafowil.widget.array`` hooks if installed
|
|
127
|
+
[rnix, 2011-10-03]
|
|
128
|
+
|
|
129
|
+
- register plan and depend on yafowil 1.3
|
|
130
|
+
[jensens, 2011-09-30]
|
|
131
|
+
|
|
132
|
+
1.2
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
- adopt to yafowil 1.2
|
|
136
|
+
[jensens, 2011-09-20]
|
|
137
|
+
|
|
138
|
+
- resource available in plain Zope2
|
|
139
|
+
[jensens, 2011-09-20]
|
|
140
|
+
|
|
141
|
+
1.1
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
- adopt to yafowil 1.1
|
|
145
|
+
[rnix, 2011-07-08]
|
|
146
|
+
|
|
147
|
+
1.0.1
|
|
148
|
+
-----
|
|
149
|
+
|
|
150
|
+
- test coverage
|
|
151
|
+
[rnix, 2011-05-07]
|
|
152
|
+
|
|
153
|
+
1.0
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
- made it work
|
|
157
|
+
[jensens, 2010-11-24]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
License
|
|
161
|
+
=======
|
|
162
|
+
|
|
163
|
+
Copyright (c) 2010-2021, BlueDynamics Alliance, Austria, Germany, Switzerland
|
|
164
|
+
Copyright (c) 2021-2022, Yafowil Contributors
|
|
165
|
+
All rights reserved.
|
|
166
|
+
|
|
167
|
+
Redistribution and use in source and binary forms, with or without
|
|
168
|
+
modification, are permitted provided that the following conditions are met:
|
|
169
|
+
|
|
170
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
171
|
+
list of conditions and the following disclaimer.
|
|
172
|
+
|
|
173
|
+
* Redistributions in binary form must reproduce the above copyright notice, this
|
|
174
|
+
list of conditions and the following disclaimer in the documentation and/or
|
|
175
|
+
other materials provided with the distribution.
|
|
176
|
+
|
|
177
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
178
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
179
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
180
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
181
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
182
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
183
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
184
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
185
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
186
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
yafowil.widget.autocomplete-2.0a1-py3.9-nspkg.pth,sha256=qMztS1qcT3Izik2RG40diPoBu5UBjZngpO4B5vlyj2I,1032
|
|
2
|
+
yafowil/widget/autocomplete/__init__.py,sha256=Td_ploHAruVP8scuFhcpTrh5gb8VNIEKZzzhYFGv08I,1644
|
|
3
|
+
yafowil/widget/autocomplete/example.py,sha256=XfxLIZZr-1vchDMQBAw4pl5bW2cwbJgKPyC1QWQnsU8,4055
|
|
4
|
+
yafowil/widget/autocomplete/tests.py,sha256=Zeo63EpkxAiTcrrvJsDwuZpWVKQ02LPri5x3unQ1Yv0,5637
|
|
5
|
+
yafowil/widget/autocomplete/widget.py,sha256=K1VXmLlyxkWy74xXQCpCTRon-GyxmiMTFPd_9yO9hY0,2432
|
|
6
|
+
yafowil/widget/autocomplete/resources/widget.css,sha256=T2WDambBG4WZnGNxN_07EMCIFZ574J5QeeTCmR6TNDs,625
|
|
7
|
+
yafowil/widget/autocomplete/resources/widget.js,sha256=P35IQ3P5YHWy1zxtzy6aaxOSUvmf7wXXMP_r-zUYX7w,13530
|
|
8
|
+
yafowil/widget/autocomplete/resources/widget.min.js,sha256=BdjBUTg83ReDi0sj1uMiIRfzC35dLMxe7J_3B_zWyXM,5967
|
|
9
|
+
yafowil.widget.autocomplete-2.0a1.dist-info/LICENSE.rst,sha256=voySO5bColCbsIXQyWy4yWhSk5DULUYHeiQ121Vh3fU,1403
|
|
10
|
+
yafowil.widget.autocomplete-2.0a1.dist-info/METADATA,sha256=yzK9GCyjTLnMiYIVoqNpoeO4le9D604c1rcFKycRZcw,4629
|
|
11
|
+
yafowil.widget.autocomplete-2.0a1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
12
|
+
yafowil.widget.autocomplete-2.0a1.dist-info/entry_points.txt,sha256=OqVe3M8KB75CHP8cJicoLRsFWtv12qlibX1P1PYCXRU,123
|
|
13
|
+
yafowil.widget.autocomplete-2.0a1.dist-info/namespace_packages.txt,sha256=oTPEdRfMCGNcZRcF2Y0ULt8JBOUbIGRQHpcQ3vb_vXQ,23
|
|
14
|
+
yafowil.widget.autocomplete-2.0a1.dist-info/top_level.txt,sha256=cJz5CZEzEKmMv88hkEh5t8fW1bRvd7Dv6VZ7l6RpqUA,8
|
|
15
|
+
yafowil.widget.autocomplete-2.0a1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
yafowil
|