Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
converse.js
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
converse.js
Commits
ad73abbd
Commit
ad73abbd
authored
Aug 14, 2018
by
JC Brand
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Rewrite as ES2015 class
parent
33cd23c5
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
185 additions
and
231 deletions
+185
-231
src/converse-autocomplete.js
src/converse-autocomplete.js
+185
-231
No files found.
src/converse-autocomplete.js
View file @
ad73abbd
...
...
@@ -20,120 +20,147 @@
const
{
_converse
}
=
this
;
_converse
.
FILTER_CONTAINS
=
function
(
text
,
input
)
{
return
RegExp
(
$
.
regExpEscape
(
input
.
trim
()),
"
i
"
).
test
(
text
);
return
RegExp
(
helpers
.
regExpEscape
(
input
.
trim
()),
"
i
"
).
test
(
text
);
};
_converse
.
FILTER_STARTSWITH
=
function
(
text
,
input
)
{
return
RegExp
(
"
^
"
+
$
.
regExpEscape
(
input
.
trim
()),
"
i
"
).
test
(
text
);
return
RegExp
(
"
^
"
+
helpers
.
regExpEscape
(
input
.
trim
()),
"
i
"
).
test
(
text
);
};
const
_ac
=
function
(
el
,
o
)
{
const
me
=
this
;
const
SORT_BYLENGTH
=
function
(
a
,
b
)
{
if
(
a
.
length
!==
b
.
length
)
{
return
a
.
length
-
b
.
length
;
}
return
a
<
b
?
-
1
:
1
;
};
const
REPLACE
=
(
text
)
=>
(
this
.
input
.
value
=
text
.
value
);
const
ITEM
=
(
text
,
input
)
=>
{
input
=
input
.
trim
();
const
element
=
document
.
createElement
(
"
li
"
);
element
.
setAttribute
(
"
aria-selected
"
,
"
false
"
);
const
regex
=
new
RegExp
(
"
(
"
+
input
+
"
)
"
,
"
ig
"
);
const
parts
=
input
?
text
.
split
(
regex
)
:
[
text
];
parts
.
forEach
((
txt
)
=>
{
if
(
input
&&
txt
.
match
(
regex
))
{
const
match
=
document
.
createElement
(
"
mark
"
);
match
.
textContent
=
txt
;
element
.
appendChild
(
match
);
}
else
{
element
.
appendChild
(
document
.
createTextNode
(
txt
));
}
});
return
element
;
};
class
AutoComplete
{
constructor
(
el
,
o
)
{
this
.
is_opened
=
false
;
this
.
is_opened
=
false
;
if
(
u
.
hasClass
(
'
.suggestion-box
'
,
el
))
{
this
.
container
=
el
;
}
else
{
this
.
container
=
el
.
querySelector
(
'
.suggestion-box
'
);
}
this
.
input
=
this
.
container
.
querySelector
(
'
.suggestion-box__input
'
);
this
.
input
.
setAttribute
(
"
autocomplete
"
,
"
off
"
);
this
.
input
.
setAttribute
(
"
aria-autocomplete
"
,
"
list
"
);
this
.
ul
=
this
.
container
.
querySelector
(
'
.suggestion-box__results
'
);
this
.
status
=
this
.
container
.
querySelector
(
'
.suggestion-box__additions
'
);
o
=
o
||
{};
_
.
assignIn
(
this
,
{
'
match_current_word
'
:
false
,
// Match only the current word, otherwise all input is matched
'
match_on_tab
'
:
false
,
// Whether matching should only start when tab's pressed
'
min_chars
'
:
2
,
'
max_items
'
:
10
,
'
auto_evaluate
'
:
true
,
'
auto_first
'
:
false
,
'
data
'
:
_
.
identity
,
'
filter
'
:
_converse
.
FILTER_CONTAINS
,
'
sort
'
:
o
.
sort
===
false
?
false
:
SORT_BYLENGTH
,
'
item
'
:
ITEM
,
'
replace
'
:
REPLACE
},
o
);
this
.
index
=
-
1
;
if
(
u
.
hasClass
(
'
.suggestion-box
'
,
el
))
{
this
.
container
=
el
;
}
else
{
this
.
container
=
el
.
querySelector
(
'
.suggestion-box
'
);
const
input
=
{
"
blur
"
:
this
.
close
.
bind
(
this
,
{
'
reason
'
:
"
blur
"
}),
"
keydown
"
:
()
=>
this
.
onKeyDown
()
}
if
(
this
.
auto_evaluate
)
{
input
[
"
input
"
]
=
this
.
evaluate
.
bind
(
this
);
}
this
.
bindEvents
(
input
)
if
(
this
.
input
.
hasAttribute
(
"
list
"
))
{
this
.
list
=
"
#
"
+
this
.
input
.
getAttribute
(
"
list
"
);
this
.
input
.
removeAttribute
(
"
list
"
);
}
else
{
this
.
list
=
this
.
input
.
getAttribute
(
"
data-list
"
)
||
o
.
list
||
[];
}
}
this
.
input
=
$
(
this
.
container
.
querySelector
(
'
.suggestion-box__input
'
));
this
.
input
.
setAttribute
(
"
autocomplete
"
,
"
off
"
);
this
.
input
.
setAttribute
(
"
aria-autocomplete
"
,
"
list
"
);
this
.
ul
=
$
(
this
.
container
.
querySelector
(
'
.suggestion-box__results
'
));
this
.
status
=
$
(
this
.
container
.
querySelector
(
'
.suggestion-box__additions
'
));
o
=
o
||
{};
configure
(
this
,
{
'
match_current_word
'
:
false
,
// Match only the current word, otherwise all input is matched
'
match_on_tab
'
:
false
,
// Whether matching should only start when tab's pressed
'
min_chars
'
:
2
,
'
max_items
'
:
10
,
'
auto_evaluate
'
:
true
,
'
auto_first
'
:
false
,
'
data
'
:
_ac
.
DATA
,
'
filter
'
:
_ac
.
FILTER_CONTAINS
,
'
sort
'
:
o
.
sort
===
false
?
false
:
_ac
.
SORT_BYLENGTH
,
'
item
'
:
_ac
.
ITEM
,
'
replace
'
:
_ac
.
REPLACE
},
o
);
this
.
index
=
-
1
;
const
input
=
{
"
blur
"
:
this
.
close
.
bind
(
this
,
{
reason
:
"
blur
"
}),
"
keydown
"
:
function
(
evt
)
{
const
c
=
evt
.
keyCode
;
// If the dropdown `ul` is in view, then act on keydown for the following keys:
// Enter / Esc / Up / Down
if
(
me
.
opened
)
{
if
(
c
===
_converse
.
keycodes
.
ENTER
&&
me
.
selected
)
{
evt
.
preventDefault
();
me
.
select
();
}
else
if
(
c
===
_converse
.
keycodes
.
ESCAPE
)
{
me
.
close
({
reason
:
"
esc
"
});
}
else
if
(
c
===
_converse
.
keycodes
.
UP_ARROW
||
c
===
_converse
.
keycodes
.
DOWN_ARROW
)
{
evt
.
preventDefault
();
me
[
c
===
_converse
.
keycodes
.
UP_ARROW
?
"
previous
"
:
"
next
"
]();
}
onKeyDown
(
evt
)
{
const
c
=
evt
.
keyCode
;
// If the dropdown `ul` is in view, then act on keydown for the following keys:
// Enter / Esc / Up / Down
if
(
this
.
opened
)
{
if
(
c
===
_converse
.
keycodes
.
ENTER
&&
this
.
selected
)
{
evt
.
preventDefault
();
this
.
select
();
}
else
if
(
c
===
_converse
.
keycodes
.
ESCAPE
)
{
this
.
close
({
reason
:
"
esc
"
});
}
else
if
(
c
===
_converse
.
keycodes
.
UP_ARROW
||
c
===
_converse
.
keycodes
.
DOWN_ARROW
)
{
evt
.
preventDefault
();
this
[
c
===
_converse
.
keycodes
.
UP_ARROW
?
"
previous
"
:
"
next
"
]();
}
}
}
if
(
this
.
auto_evaluate
)
{
input
[
"
input
"
]
=
this
.
evaluate
.
bind
(
this
);
}
// Bind events
this
.
_events
=
{
'
input
'
:
input
,
'
form
'
:
{
"
submit
"
:
this
.
close
.
bind
(
this
,
{
reason
:
"
submit
"
})
},
'
ul
'
:
{
"
mousedown
"
:
function
(
evt
)
{
let
li
=
evt
.
target
;
if
(
li
!==
this
)
{
while
(
li
&&
!
(
/li/i
).
test
(
li
.
nodeName
))
{
li
=
li
.
parentNode
;
}
bindEvents
(
input
)
{
// Bind events
this
.
_events
=
{
'
input
'
:
input
,
'
form
'
:
{
"
submit
"
:
this
.
close
.
bind
(
this
,
{
reason
:
"
submit
"
})
},
'
ul
'
:
{
"
mousedown
"
:
(
evt
)
=>
{
let
li
=
evt
.
target
;
if
(
li
!==
this
)
{
while
(
li
&&
!
(
/li/i
).
test
(
li
.
nodeName
))
{
li
=
li
.
parentNode
;
}
if
(
li
&&
evt
.
button
===
0
)
{
// Only select on left click
evt
.
preventDefault
();
me
.
select
(
li
,
evt
.
target
);
if
(
li
&&
evt
.
button
===
0
)
{
// Only select on left click
evt
.
preventDefault
();
this
.
select
(
li
,
evt
.
target
);
}
}
}
}
}
};
$
.
bind
(
this
.
input
,
this
.
_events
.
input
);
$
.
bind
(
this
.
input
.
form
,
this
.
_events
.
form
);
$
.
bind
(
this
.
ul
,
this
.
_events
.
ul
);
if
(
this
.
input
.
hasAttribute
(
"
list
"
))
{
this
.
list
=
"
#
"
+
this
.
input
.
getAttribute
(
"
list
"
);
this
.
input
.
removeAttribute
(
"
list
"
);
}
else
{
this
.
list
=
this
.
input
.
getAttribute
(
"
data-list
"
)
||
o
.
list
||
[];
};
helpers
.
bind
(
this
.
input
,
this
.
_events
.
input
);
helpers
.
bind
(
this
.
input
.
form
,
this
.
_events
.
form
);
helpers
.
bind
(
this
.
ul
,
this
.
_events
.
ul
);
}
_ac
.
all
.
push
(
this
);
}
_ac
.
prototype
=
{
set
list
(
list
)
{
if
(
Array
.
isArray
(
list
))
{
if
(
Array
.
isArray
(
list
)
||
typeof
list
===
"
function
"
)
{
this
.
_list
=
list
;
}
else
if
(
typeof
list
===
"
string
"
&&
_
.
includes
(
list
,
"
,
"
))
{
}
else
if
(
typeof
list
===
"
string
"
&&
_
.
includes
(
list
,
"
,
"
))
{
this
.
_list
=
list
.
split
(
/
\s
*,
\s
*/
);
}
else
{
// Element or CSS selector
list
=
$
(
list
);
}
else
{
// Element or CSS selector
list
=
helpers
.
getElement
(
list
);
if
(
list
&&
list
.
children
)
{
const
items
=
[];
slice
.
apply
(
list
.
children
).
forEach
(
function
(
el
)
{
...
...
@@ -153,27 +180,26 @@
if
(
document
.
activeElement
===
this
.
input
)
{
this
.
evaluate
();
}
}
,
}
get
selected
()
{
get
selected
()
{
return
this
.
index
>
-
1
;
}
,
}
get
opened
()
{
get
opened
()
{
return
this
.
is_opened
;
}
,
}
close
(
o
)
{
if
(
!
this
.
opened
)
{
return
;
}
this
.
ul
.
setAttribute
(
"
hidden
"
,
""
);
this
.
is_opened
=
false
;
this
.
index
=
-
1
;
$
.
fire
(
this
.
input
,
"
suggestion-box-close
"
,
o
||
{});
}
,
helpers
.
fire
(
this
.
input
,
"
suggestion-box-close
"
,
o
||
{});
}
open
()
{
this
.
ul
.
removeAttribute
(
"
hidden
"
);
...
...
@@ -183,13 +209,13 @@
this
.
goto
(
0
);
}
$
.
fire
(
this
.
input
,
"
suggestion-box-open
"
);
}
,
helpers
.
fire
(
this
.
input
,
"
suggestion-box-open
"
);
}
destroy
()
{
//remove events from the input and its form
$
.
unbind
(
this
.
input
,
this
.
_events
.
input
);
$
.
unbind
(
this
.
input
.
form
,
this
.
_events
.
form
);
helpers
.
unbind
(
this
.
input
,
this
.
_events
.
input
);
helpers
.
unbind
(
this
.
input
.
form
,
this
.
_events
.
form
);
//move the input out of the suggestion-box container and remove the container and its children
const
parentNode
=
this
.
container
.
parentNode
;
...
...
@@ -200,26 +226,18 @@
//remove autocomplete and aria-autocomplete attributes
this
.
input
.
removeAttribute
(
"
autocomplete
"
);
this
.
input
.
removeAttribute
(
"
aria-autocomplete
"
);
//remove this awesomeplete instance from the global array of instances
var
indexOfAutoComplete
=
_ac
.
all
.
indexOf
(
this
);
if
(
indexOfAutoComplete
!==
-
1
)
{
_ac
.
all
.
splice
(
indexOfAutoComplete
,
1
);
}
},
}
next
()
{
var
count
=
this
.
ul
.
children
.
length
;
const
count
=
this
.
ul
.
children
.
length
;
this
.
goto
(
this
.
index
<
count
-
1
?
this
.
index
+
1
:
(
count
?
0
:
-
1
)
);
}
,
}
previous
()
{
var
count
=
this
.
ul
.
children
.
length
;
var
pos
=
this
.
index
-
1
;
const
count
=
this
.
ul
.
children
.
length
,
pos
=
this
.
index
-
1
;
this
.
goto
(
this
.
selected
&&
pos
!==
-
1
?
pos
:
count
-
1
);
}
,
}
// Should not be used, highlights specific item without any checks!
goto
(
i
)
{
...
...
@@ -238,11 +256,11 @@
// scroll to highlighted element in case parent's height is fixed
this
.
ul
.
scrollTop
=
lis
[
i
].
offsetTop
-
this
.
ul
.
clientHeight
+
lis
[
i
].
clientHeight
;
$
.
fire
(
this
.
input
,
"
suggestion-box-highlight
"
,
{
helpers
.
fire
(
this
.
input
,
"
suggestion-box-highlight
"
,
{
text
:
this
.
suggestions
[
this
.
index
]
});
}
}
,
}
select
(
selected
,
origin
)
{
if
(
selected
)
{
...
...
@@ -253,7 +271,7 @@
if
(
selected
)
{
const
suggestion
=
this
.
suggestions
[
this
.
index
],
allowed
=
$
.
fire
(
this
.
input
,
"
suggestion-box-select
"
,
{
allowed
=
helpers
.
fire
(
this
.
input
,
"
suggestion-box-select
"
,
{
'
text
'
:
suggestion
,
'
origin
'
:
origin
||
selected
});
...
...
@@ -265,7 +283,7 @@
this
.
trigger
(
"
suggestion-box-selectcomplete
"
,
{
'
text
'
:
suggestion
});
}
}
}
,
}
keyPressed
(
ev
)
{
if
(
_
.
includes
([
...
...
@@ -284,7 +302,7 @@
if
(
this
.
auto_completing
)
{
this
.
evaluate
();
}
}
,
}
evaluate
(
ev
)
{
let
value
=
this
.
input
.
value
;
...
...
@@ -292,12 +310,14 @@
value
=
u
.
getCurrentWord
(
this
.
input
);
}
if
(
value
.
length
>=
this
.
min_chars
&&
this
.
_list
.
length
>
0
)
{
const
list
=
typeof
this
.
_list
===
"
function
"
?
this
.
_list
()
:
this
.
_list
;
if
(
value
.
length
>=
this
.
min_chars
&&
list
.
length
>
0
)
{
this
.
index
=
-
1
;
// Populate list with options that match
this
.
ul
.
innerHTML
=
""
;
this
.
suggestions
=
this
.
_
list
this
.
suggestions
=
list
.
map
(
item
=>
new
Suggestion
(
this
.
data
(
item
,
value
)))
.
filter
(
item
=>
this
.
filter
(
item
,
value
));
...
...
@@ -317,46 +337,11 @@
this
.
auto_completing
=
false
;
}
}
}
;
}
// Make it an event emitter
_
.
extend
(
_ac
.
prototype
,
Backbone
.
Events
);
_
.
extend
(
AutoComplete
.
prototype
,
Backbone
.
Events
);
// Static methods/properties
_ac
.
all
=
[];
_ac
.
SORT_BYLENGTH
=
function
(
a
,
b
)
{
if
(
a
.
length
!==
b
.
length
)
{
return
a
.
length
-
b
.
length
;
}
return
a
<
b
?
-
1
:
1
;
};
_ac
.
ITEM
=
function
(
text
,
input
)
{
input
=
input
.
trim
();
var
element
=
document
.
createElement
(
"
li
"
);
element
.
setAttribute
(
"
aria-selected
"
,
"
false
"
);
var
regex
=
new
RegExp
(
"
(
"
+
input
+
"
)
"
,
"
ig
"
);
var
parts
=
input
?
text
.
split
(
regex
)
:
[
text
];
parts
.
forEach
(
function
(
txt
)
{
if
(
input
&&
txt
.
match
(
regex
))
{
var
match
=
document
.
createElement
(
"
mark
"
);
match
.
textContent
=
txt
;
element
.
appendChild
(
match
);
}
else
{
element
.
appendChild
(
document
.
createTextNode
(
txt
));
}
});
return
element
;
};
_ac
.
REPLACE
=
function
(
text
)
{
this
.
input
.
value
=
text
.
value
;
};
_ac
.
DATA
=
function
(
item
/*, input*/
)
{
return
item
;
};
// Private functions
...
...
@@ -377,89 +362,58 @@
return
""
+
this
.
label
;
};
function
configure
(
instance
,
properties
,
o
)
{
for
(
var
i
in
properties
)
{
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
properties
,
i
))
{
continue
;
}
const
initial
=
properties
[
i
],
attr_value
=
instance
.
input
.
getAttribute
(
"
data-
"
+
i
.
toLowerCase
());
if
(
typeof
initial
===
"
number
"
)
{
instance
[
i
]
=
parseInt
(
attr_value
,
10
);
}
else
if
(
initial
===
false
)
{
// Boolean options must be false by default anyway
instance
[
i
]
=
attr_value
!==
null
;
}
else
if
(
initial
instanceof
Function
)
{
instance
[
i
]
=
null
;
}
else
{
instance
[
i
]
=
attr_value
;
}
if
(
!
instance
[
i
]
&&
instance
[
i
]
!==
0
)
{
instance
[
i
]
=
(
i
in
o
)?
o
[
i
]
:
initial
;
}
}
}
// Helpers
var
slice
=
Array
.
prototype
.
slice
;
function
$
(
expr
,
con
)
{
return
typeof
expr
===
"
string
"
?
(
con
||
document
).
querySelector
(
expr
)
:
expr
||
null
;
}
const
helpers
=
{
function
$$
(
expr
,
con
)
{
return
slice
.
call
((
con
||
document
).
querySelectorAll
(
expr
))
;
}
getElement
(
expr
,
el
)
{
return
typeof
expr
===
"
string
"
?
(
el
||
document
).
querySelector
(
expr
)
:
expr
||
null
;
},
$
.
bind
=
function
(
element
,
o
)
{
if
(
element
)
{
for
(
var
event
in
o
)
{
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
o
,
event
))
{
continue
;
bind
(
element
,
o
)
{
if
(
element
)
{
for
(
var
event
in
o
)
{
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
o
,
event
))
{
continue
;
}
const
callback
=
o
[
event
];
event
.
split
(
/
\s
+/
).
forEach
(
event
=>
element
.
addEventListener
(
event
,
callback
));
}
const
callback
=
o
[
event
];
event
.
split
(
/
\s
+/
).
forEach
(
event
=>
element
.
addEventListener
(
event
,
callback
));
}
}
};
},
$
.
unbind
=
function
(
element
,
o
)
{
if
(
element
)
{
for
(
var
event
in
o
)
{
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
o
,
event
))
{
continue
;
unbind
(
element
,
o
)
{
if
(
element
)
{
for
(
var
event
in
o
)
{
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
o
,
event
))
{
continue
;
}
const
callback
=
o
[
event
];
event
.
split
(
/
\s
+/
).
forEach
(
event
=>
element
.
removeEventListener
(
event
,
callback
));
}
const
callback
=
o
[
event
];
event
.
split
(
/
\s
+/
).
forEach
(
event
=>
element
.
removeEventListener
(
event
,
callback
));
}
}
};
$
.
fire
=
function
(
target
,
type
,
properties
)
{
var
evt
=
document
.
createEvent
(
"
HTMLEvents
"
);
},
evt
.
initEvent
(
type
,
true
,
true
);
fire
(
target
,
type
,
properties
)
{
const
evt
=
document
.
createEvent
(
"
HTMLEvents
"
);
evt
.
initEvent
(
type
,
true
,
true
);
for
(
var
j
in
properties
)
{
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
properties
,
j
))
{
continue
;
for
(
var
j
in
properties
)
{
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
properties
,
j
))
{
continue
;
}
evt
[
j
]
=
properties
[
j
];
}
evt
[
j
]
=
properties
[
j
];
}
return
target
.
dispatchEvent
(
evt
);
};
$
.
regExpEscape
=
function
(
s
)
{
return
s
.
replace
(
/
[
-
\\
^$*+?.()|[
\]
{}
]
/g
,
"
\\
$&
"
);
};
return
target
.
dispatchEvent
(
evt
);
},
_ac
.
$
=
$
;
_ac
.
$$
=
$$
;
regExpEscape
(
s
)
{
return
s
.
replace
(
/
[
-
\\
^$*+?.()|[
\]
{}
]
/g
,
"
\\
$&
"
);
}
}
_converse
.
AutoComplete
=
_ac
;
_converse
.
AutoComplete
=
AutoComplete
;
}
});
}));
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment