Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
typo3
qfq
Commits
a9953016
Commit
a9953016
authored
Feb 07, 2020
by
Marc Egger
Browse files
Marc tags typeahead
parent
069e3296
Changes
4
Expand all
Hide whitespace changes
Inline
Side-by-side
javascript/src/Helper/tagManager.js
0 → 100644
View file @
a9953016
This diff is collapsed.
Click to expand it.
javascript/src/TypeAhead.js
View file @
a9953016
...
...
@@ -25,9 +25,11 @@ var QfqNS = QfqNS || {};
n
.
TypeAhead
.
install
=
function
(
typeahead_endpoint
)
{
$
(
'
.qfq-typeahead
'
).
each
(
function
()
{
var
$shadowElement
,
bloodhoundConfiguration
;
var
bloodhoundConfiguration
;
var
$element
=
$
(
this
);
// bloodhound is used to get the remote data (suggestions)
bloodhoundConfiguration
=
{
// We need to be notified on success, so we need a promise
initialize
:
false
,
...
...
@@ -41,94 +43,221 @@ var QfqNS = QfqNS || {};
wildcard
:
'
%QUERY
'
}
};
if
(
$element
.
val
()
!==
''
)
{
// We prefetch the value provided
bloodhoundConfiguration
.
prefetch
=
{};
bloodhoundConfiguration
.
prefetch
.
url
=
n
.
TypeAhead
.
makePrefetchUrl
(
typeahead_endpoint
,
$element
.
val
(),
$element
);
// Disable cache, we expect only a few entries. Caching gives sometimes strange behavior.
bloodhoundConfiguration
.
prefetch
.
cache
=
false
;
// initialize typeahead (either with or without tags)
if
(
$element
.
data
(
'
typeahead-tags
'
))
{
n
.
TypeAhead
.
installWithTags
(
$element
,
bloodhoundConfiguration
);
}
else
{
n
.
TypeAhead
.
installWithoutTags
(
typeahead_endpoint
,
$element
,
bloodhoundConfiguration
)
;
}
$shadowElement
=
n
.
TypeAhead
.
makeShadowElement
(
$element
);
var
suggestions
=
new
Bloodhound
(
bloodhoundConfiguration
);
var
promise
=
suggestions
.
initialize
();
promise
.
done
((
function
(
$element
,
suggestions
)
{
return
function
()
{
n
.
TypeAhead
.
fillTypeAheadFromShadowElement
(
$element
,
suggestions
);
};
})(
$element
,
suggestions
));
});
};
n
.
TypeAhead
.
installWithTags
=
function
(
$element
,
bloodhoundConfiguration
)
{
$element
.
typeahead
({
hint
:
n
.
TypeAhead
.
getHint
(
$element
),
highlight
:
n
.
TypeAhead
.
getHighlight
(
$element
),
minLength
:
n
.
TypeAhead
.
getMinLength
(
$element
)
},
{
display
:
'
value
'
,
source
:
suggestions
,
limit
:
n
.
TypeAhead
.
getLimit
(
$element
),
templates
:
{
suggestion
:
function
(
obj
)
{
return
"
<div>
"
+
n
.
TypeAhead
.
htmlEncode
(
obj
.
value
)
+
"
</div>
"
;
},
// No message if field is not set to pedantic.
notFound
:
(
function
(
$_
)
{
return
function
(
obj
)
{
if
(
!!
$_
.
data
(
'
typeahead-pedantic
'
))
return
"
<div>'
"
+
n
.
TypeAhead
.
htmlEncode
(
obj
.
query
)
+
"
' not found
"
;
};
})(
$element
)
}
});
// initialize bloodhound (typeahead suggestion engine)
var
suggestions
=
new
Bloodhound
(
bloodhoundConfiguration
);
suggestions
.
initialize
();
// prevent form submit when enter key is pressed
$element
.
off
(
'
keyup
'
);
$element
.
on
(
'
keypress keyup
'
,
function
(
e
)
{
var
code
=
e
.
keyCode
||
e
.
which
;
if
(
code
===
13
)
{
e
.
preventDefault
();
return
false
;
}
});
$element
.
bind
(
'
typeahead:select typeahead:autocomplete
'
,
function
(
event
,
suggestion
)
{
var
$shadowElement
=
n
.
TypeAhead
.
getShadowElement
(
$
(
event
.
delegateTarget
));
$shadowElement
.
val
(
suggestion
.
key
);
});
// list to keep tracks of existing tags and those added during the current session
// expected JSON format: [{value: "Alaska", key: "AK"}, {value: "Alabama", key: "AL"}]
var
existingTags
=
$element
.
val
()
!==
''
?
JSON
.
parse
(
$element
.
val
())
:
[];
if
(
!!
$element
.
data
(
'
typeahead-pedantic
'
))
{
$element
.
bind
(
'
typeahead:change
'
,
n
.
TypeAhead
.
makePedanticHandler
(
suggestions
));
$element
.
on
(
'
keydown
'
,
(
function
(
suggestions
)
{
return
function
(
event
)
{
if
(
event
.
which
===
13
)
{
n
.
TypeAhead
.
makePedanticHandler
(
suggestions
)(
event
);
}
};
})(
suggestions
));
// The pedantic handler will test if the shadow element has a value set (the KEY). If not, the
// typeahead element is cleared. Thus we have to guarantee that no value exists in the shadow
// element the instant the user starts typing since we don't know the outcome of the search.
//
// If we don't clear the shadow element the instant the user starts typing, and simply let the
// `typeahead:select` or `typeahead:autocomplete` handler set the selected value, the
// user might do following steps and end up in an inconsistent state:
//
// 1. Use typeahead to select/autocomplete a suggestion
// 2. delete the suggestion
// 3. enter a random string
// 4. submit form
//
// This would leave a stale value in the shadow element (from step 1.), and the pedantic handler
// would not clear the typeahead element, giving the impression the value in the typeahead element will be submitted.
$element
.
on
(
'
input
'
,
(
function
(
$shadowElement
)
{
return
function
()
{
$shadowElement
.
val
(
''
);
};
})(
$shadowElement
));
// list of current typeahead suggestions
var
typeaheadList
=
existingTags
.
slice
();
// get list of possible keys a user can press to push a tag (list of keycodes)
var
delimiters
=
$element
.
data
(
'
typeahead-tag-delimiters
'
);
delimiters
=
delimiters
!==
undefined
?
delimiters
:
[
9
,
13
,
44
];
// validator function for pedantic mode
var
pedanticValidator
=
function
(
tag
)
{
// check if tag is in typeahead suggestions
var
tagLookup
=
typeaheadList
.
filter
(
function
(
t
)
{
return
t
.
value
.
toLowerCase
()
===
tag
.
toLowerCase
();})[
0
];
return
tagLookup
!==
undefined
;
};
// initialize tagsManager
var
tagApi
=
$element
.
tagsManager
({
deleteTagsOnBackspace
:
false
,
hiddenTagListName
:
$element
.
attr
(
'
name
'
),
tagClass
:
'
qfq-typeahead-tag
'
,
delimiters
:
delimiters
,
validator
:
!!
$element
.
data
(
'
typeahead-pedantic
'
)
?
pedanticValidator
:
null
,
});
// when tag is pushed, look up key and add it to existingTags
tagApi
.
bind
(
'
tm:pushed
'
,
function
(
e
,
tag
)
{
var
tagLookup
=
typeaheadList
.
filter
(
function
(
t
)
{
return
t
.
value
.
toLowerCase
()
===
tag
.
toLowerCase
();})[
0
];
if
(
undefined
===
tagLookup
)
{
existingTags
.
push
({
key
:
0
,
value
:
tag
});
}
else
{
$element
.
bind
(
'
typeahead:change
'
,
function
(
event
,
suggestion
)
{
var
$shadowElement
=
n
.
TypeAhead
.
getShadowElement
(
$
(
event
.
delegateTarget
));
// If none pendatic, suggestion key might not exist, so use suggestion instead.
$shadowElement
.
val
(
suggestion
.
key
||
suggestion
);
});
existingTags
.
push
({
key
:
tagLookup
.
key
,
value
:
tagLookup
.
value
});
}
});
// when the hidden input field changes, overwrite value with tag object list
tagApi
.
bind
(
'
tm:hiddenUpdate
'
,
function
(
e
,
tags
,
hiddenInput
)
{
var
tagObjects
=
$
.
map
(
tags
,
function
(
t
)
{
return
existingTags
.
filter
(
function
(
tt
)
{
return
tt
.
value
===
t
;})[
0
];
});
hiddenInput
.
val
(
JSON
.
stringify
(
tagObjects
));
});
$element
.
removeAttr
(
'
name
'
);
// add existing tags
$
.
each
(
existingTags
,
function
(
i
,
tag
)
{
tagApi
.
tagsManager
(
'
pushTag
'
,
tag
.
value
);
});
// add typahead
$element
.
typeahead
({
// options
hint
:
n
.
TypeAhead
.
getHint
(
$element
),
highlight
:
n
.
TypeAhead
.
getHighlight
(
$element
),
minLength
:
n
.
TypeAhead
.
getMinLength
(
$element
)
},
{
display
:
'
value
'
,
source
:
suggestions
,
limit
:
n
.
TypeAhead
.
getLimit
(
$element
),
templates
:
{
suggestion
:
function
(
obj
)
{
return
"
<div>
"
+
n
.
TypeAhead
.
htmlEncode
(
obj
.
value
)
+
"
</div>
"
;
},
// No message if field is not set to pedantic.
notFound
:
(
function
(
$_
)
{
return
function
(
obj
)
{
if
(
!!
$_
.
data
(
'
typeahead-pedantic
'
))
return
"
<div>'
"
+
n
.
TypeAhead
.
htmlEncode
(
obj
.
query
)
+
"
' not found
"
;
};
})(
$element
)
}
});
// directly add tag when clicked on in typahead menu
$element
.
bind
(
'
typeahead:selected
'
,
function
(
event
,
sugg
)
{
tagApi
.
tagsManager
(
"
pushTag
"
,
sugg
.
value
);
});
// update typahead list when typahead changes
$element
.
bind
(
'
typeahead:render
'
,
function
(
event
,
sugg
)
{
typeaheadList
.
length
=
0
;
typeaheadList
.
push
.
apply
(
typeaheadList
,
sugg
);
});
};
n
.
TypeAhead
.
installWithoutTags
=
function
(
typeahead_endpoint
,
$element
,
bloodhoundConfiguration
)
{
var
$shadowElement
;
// Prefetch the value that is already in the field
if
(
$element
.
val
()
!==
''
)
{
bloodhoundConfiguration
.
prefetch
=
{};
bloodhoundConfiguration
.
prefetch
.
url
=
n
.
TypeAhead
.
makePrefetchUrl
(
typeahead_endpoint
,
$element
.
val
(),
$element
);
// Disable cache, we expect only a few entries. Caching gives sometimes strange behavior.
bloodhoundConfiguration
.
prefetch
.
cache
=
false
;
}
// create a shadow element with the same value. This seems to be important for the pedantic mode. (?)
$shadowElement
=
n
.
TypeAhead
.
makeShadowElement
(
$element
);
// prefetch data
var
suggestions
=
new
Bloodhound
(
bloodhoundConfiguration
);
var
promise
=
suggestions
.
initialize
();
// use shadow element to back fill field value, if it is in the fetched suggestions (why?)
promise
.
done
((
function
(
$element
,
suggestions
)
{
return
function
()
{
n
.
TypeAhead
.
fillTypeAheadFromShadowElement
(
$element
,
suggestions
);
};
})(
$element
,
suggestions
));
$element
.
typeahead
({
// options
hint
:
n
.
TypeAhead
.
getHint
(
$element
),
highlight
:
n
.
TypeAhead
.
getHighlight
(
$element
),
minLength
:
n
.
TypeAhead
.
getMinLength
(
$element
)
},
{
// dataset
display
:
'
value
'
,
source
:
suggestions
,
limit
:
n
.
TypeAhead
.
getLimit
(
$element
),
templates
:
{
suggestion
:
function
(
obj
)
{
return
"
<div>
"
+
n
.
TypeAhead
.
htmlEncode
(
obj
.
value
)
+
"
</div>
"
;
},
// No message if field is not set to pedantic.
notFound
:
(
function
(
$_
)
{
return
function
(
obj
)
{
if
(
!!
$_
.
data
(
'
typeahead-pedantic
'
))
return
"
<div>'
"
+
n
.
TypeAhead
.
htmlEncode
(
obj
.
query
)
+
"
' not found
"
;
};
})(
$element
)
}
});
// bind select and autocomplete events
$element
.
bind
(
'
typeahead:select typeahead:autocomplete
'
,
function
(
event
,
suggestion
)
{
var
$shadowElement
=
n
.
TypeAhead
.
getShadowElement
(
$
(
event
.
delegateTarget
));
$shadowElement
.
val
(
suggestion
.
key
);
});
// bind change event
if
(
!!
$element
.
data
(
'
typeahead-pedantic
'
))
{
// Typeahead pedantic: Only allow suggested inputs
$element
.
bind
(
'
typeahead:change
'
,
n
.
TypeAhead
.
makePedanticHandler
(
suggestions
));
$element
.
on
(
'
keydown
'
,
(
function
(
suggestions
)
{
return
function
(
event
)
{
if
(
event
.
which
===
13
)
{
n
.
TypeAhead
.
makePedanticHandler
(
suggestions
)(
event
);
}
};
})(
suggestions
));
// The pedantic handler will test if the shadow element has a value set (the KEY). If not, the
// typeahead element is cleared. Thus we have to guarantee that no value exists in the shadow
// element the instant the user starts typing since we don't know the outcome of the search.
//
// If we don't clear the shadow element the instant the user starts typing, and simply let the
// `typeahead:select` or `typeahead:autocomplete` handler set the selected value, the
// user might do following steps and end up in an inconsistent state:
//
// 1. Use typeahead to select/autocomplete a suggestion
// 2. delete the suggestion
// 3. enter a random string
// 4. submit form
//
// This would leave a stale value in the shadow element (from step 1.), and the pedantic handler
// would not clear the typeahead element, giving the impression the value in the typeahead element will be submitted.
$element
.
on
(
'
input
'
,
(
function
(
$shadowElement
)
{
return
function
()
{
$shadowElement
.
val
(
''
);
};
})(
$shadowElement
));
}
else
{
$element
.
bind
(
'
typeahead:change
'
,
function
(
event
,
suggestion
)
{
var
$shadowElement
=
n
.
TypeAhead
.
getShadowElement
(
$
(
event
.
delegateTarget
));
// If none pendatic, suggestion key might not exist, so use suggestion instead.
$shadowElement
.
val
(
suggestion
.
key
||
suggestion
);
});
}
};
n
.
TypeAhead
.
makePedanticHandler
=
function
(
bloodhound
)
{
return
function
(
event
)
{
var
$typeAhead
=
$
(
event
.
delegateTarget
);
...
...
less/qfq-bs.css.less
View file @
a9953016
...
...
@@ -665,6 +665,11 @@ select.qfq-locked:invalid {
width: 100%;
}
// typeAhead tags
span.qfq-typeahead-tag {
margin-right: 5px;
}
@media (min-width: 768px) {
.form-horizontal .control-label {
text-align: unset;
...
...
mockup/typeahead.
html
→
mockup/typeahead.
php
View file @
a9953016
...
...
@@ -124,6 +124,56 @@
</div>
<?php
$tags
=
[
[
'value'
=>
"Alabama"
,
'key'
=>
"AL"
],
[
'value'
=>
"Alaska"
,
'key'
=>
"AK"
]
];
$tagsSafeJson
=
htmlentities
(
json_encode
(
$tags
),
ENT_QUOTES
,
'UTF-8'
);
?>
<div
id=
"formgroup4"
class=
"form-group"
>
<div
class=
"col-md-2"
>
<label
for=
"tags1"
class=
"control-label"
>
Text input 4 (tags)
</label>
</div>
<div
class=
"col-md-6"
>
<input
id=
"tags1"
type=
"text"
class=
"form-control qfq-typeahead"
name=
"tags1"
data-typeahead-sip=
"abcdef"
data-typeahead-limit=
"10"
data-typeahead-minlength=
"1"
data-typeahead-tags=
"true"
data-typeahead-tag-delimiters=
"[9, 13]"
value=
"
<?php
echo
$tagsSafeJson
;
?>
"
>
</div>
</div>
<div
id=
"formgroup5"
class=
"form-group"
>
<div
class=
"col-md-2"
>
<label
for=
"tags2"
class=
"control-label"
>
Text input 4 (tags, pedantic)
</label>
</div>
<div
class=
"col-md-6"
>
<input
id=
"tags2"
type=
"text"
class=
"form-control qfq-typeahead"
name=
"tags2"
data-typeahead-sip=
"abcdef"
data-typeahead-limit=
"10"
data-typeahead-minlength=
"1"
data-typeahead-tags=
"true"
data-typeahead-pedantic=
"true"
data-typeahead-tag-delimiters=
"[9, 44]"
value=
"
<?php
echo
$tagsSafeJson
;
?>
"
>
</div>
</div>
</form>
</div>
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new 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