feat(prototype): the first real 💩 prototype version
this is the first real working interface with almost what i imagined it to be like. Next: xhr implementation with requesting sources to update the possible choices
This commit is contained in:
2
src/.gitignore
vendored
Normal file
2
src/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.css
|
||||
*.map
|
||||
22
src/scss/_variables.scss
Normal file
22
src/scss/_variables.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
$spacer: 1em !default;
|
||||
|
||||
$black: #111111 !default;
|
||||
$white: #FFFFFF !default;
|
||||
$gray: #DDDDDD !default;
|
||||
$light-gray: #EFEFEF !default;
|
||||
$yellow: #FFFE47 !default;
|
||||
|
||||
$primary: #FF64B1 !default;
|
||||
$secondary: #EFEFEF !default;
|
||||
|
||||
$text: $black !default;
|
||||
$text-secondary: #BBBBBB !default;
|
||||
|
||||
$default-shadow: rgba(0,0,0,.3);
|
||||
|
||||
$border-default: solid 0.125 * $spacer $default-shadow !default;
|
||||
$border-default-transparent: solid 0.0625 * $spacer transparent !default;
|
||||
$border-radius: .5em !default;
|
||||
|
||||
$padding: $spacer * .5 $spacer * .75 !default;
|
||||
|
||||
354
src/scss/simplecomplete.scss
Normal file
354
src/scss/simplecomplete.scss
Normal file
@@ -0,0 +1,354 @@
|
||||
@use "sass:math";
|
||||
@import 'variables';
|
||||
|
||||
@keyframes pulsingAnim {
|
||||
0% {
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scrollingBg {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes highlightAnim {
|
||||
0% {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
15% {
|
||||
background: $primary;
|
||||
}
|
||||
|
||||
30% {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: $primary;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.simplecomplete {
|
||||
position: relative;
|
||||
border: $border-default;
|
||||
border-radius: $border-radius;
|
||||
background: linear-gradient(90deg, $white 0, $white 100%);
|
||||
box-sizing: border-box;
|
||||
transition: background 3s;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__input {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
&-field {
|
||||
flex: 1;
|
||||
background: white;
|
||||
padding: $padding;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
border: $border-default-transparent;
|
||||
border-radius: $border-radius * .8;
|
||||
outline: none;
|
||||
margin: .125em;
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-cancel {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
width: 2em;
|
||||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
font-size: 2em;
|
||||
cursor: pointer;
|
||||
|
||||
&:after {
|
||||
display: inline-block;
|
||||
content: "\00D7";
|
||||
color: darken($gray, 20);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transform: none;
|
||||
top: auto;
|
||||
right: auto;
|
||||
|
||||
background: $white;
|
||||
|
||||
&:after {
|
||||
display: block;
|
||||
padding: 0 $spacer;
|
||||
color: $gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__value {
|
||||
@extend .simplecomplete__input-field;
|
||||
&:not(.hide) {
|
||||
& ~ .simplecomplete__input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
> .selected-option, > .empty-item {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: $padding;
|
||||
margin: 0 .125em;
|
||||
background: $light-gray;
|
||||
color: $text-secondary;
|
||||
font-weight: 500;
|
||||
border-radius: $border-radius;
|
||||
border: $border-default-transparent;
|
||||
background: $gray;
|
||||
color: $black;
|
||||
|
||||
&:first-child {
|
||||
margin-left: -.25em;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: -.25em;
|
||||
}
|
||||
}
|
||||
|
||||
> .empty-item {
|
||||
background: $white;
|
||||
color: $text-secondary;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&.is-empty {
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-multiple, &.is-blankable {
|
||||
.simplecomplete__value > .selected-option {
|
||||
&:after {
|
||||
content: "\00D7";
|
||||
display: inline;
|
||||
margin-left: .25em;
|
||||
color: darken($text-secondary, 20);
|
||||
}
|
||||
}
|
||||
|
||||
.simplecomplete__input-cancel {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&__results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
max-width: 100%;
|
||||
background: $white;
|
||||
box-shadow: 0 .125em .25em 0 lighten($black, 60);
|
||||
z-index: 255;
|
||||
|
||||
border-radius: $border-radius;
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
position: relative;
|
||||
top: 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: $white;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&-list {
|
||||
border-radius: $border-radius;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: 30vh;
|
||||
padding: $spacer * .5;
|
||||
&-item, &-empty {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding: $spacer * .5 $spacer;
|
||||
color: $text-secondary;
|
||||
font-weight: 500;
|
||||
&:not(:last-child) {
|
||||
border-bottom: solid 1px lighten($black, 90);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
> .highlight {
|
||||
display: inline;
|
||||
color: $primary;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
font-weight: 500;
|
||||
background: $primary;
|
||||
color: $white;
|
||||
border-radius: $border-radius;
|
||||
|
||||
&:hover {
|
||||
background: darken($primary, 10);
|
||||
color: $white;
|
||||
}
|
||||
|
||||
> .highlight {
|
||||
color: $text;
|
||||
background: $yellow;
|
||||
padding: $spacer * .125 $spacer * .25;
|
||||
margin: -$spacer * .125 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
border-radius: $border-radius;
|
||||
background: $light-gray;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-blankable:not(.active) {
|
||||
.simplecomplete__value > .selected-option {
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active:not(.is-select) {
|
||||
.simplecomplete__results {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.active.is-select, &:not(.is-select) {
|
||||
.simplecomplete__input-field {
|
||||
padding: $spacer * 1.05;
|
||||
}
|
||||
}
|
||||
|
||||
&.inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: $primary;
|
||||
@media screen and (max-width: 992px) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
border-color: transparent;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
background: $gray;
|
||||
z-index: 255;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.simplecomplete__input {
|
||||
@media screen and (max-width: 992px) {
|
||||
animation-name: highlightAnim;
|
||||
animation-duration: 1.3s;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-busy {
|
||||
border-color: rgba(255,255,255,.6);
|
||||
background: $primary;
|
||||
background: linear-gradient(90deg, lighten(red, 30) 0%, lighten(yellow, 30) 25%, lighten(green, 30) 50%, lighten(blue, 30) 75%, lighten(red, 30) 100%);
|
||||
background-size: 10vw 10vw;
|
||||
animation-name: scrollingBg;
|
||||
animation-iteration-count: infinite;
|
||||
animation-duration: 3s;
|
||||
animation-timing-function: linear;
|
||||
|
||||
.simplecomplete__input-field {
|
||||
border-color: $primary;
|
||||
}
|
||||
|
||||
.simplecomplete__results-list {
|
||||
padding-top: 1em;
|
||||
&-empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:before, &:after {
|
||||
display: block;
|
||||
content: "";
|
||||
border-radius: $border-radius;
|
||||
height: 1.2em;
|
||||
background: $light-gray;
|
||||
width: 80%;
|
||||
margin: 0 1em;
|
||||
animation-name: pulsingAnim;
|
||||
animation-iteration-count: infinite;
|
||||
animation-duration: .8s;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
&:after {
|
||||
width: 60%;
|
||||
margin-top: .6em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.has-results) {
|
||||
.simplecomplete__results {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/simplecomplete/index.js
Normal file
7
src/simplecomplete/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import SimpleComplete from "./simplecomplete.js";
|
||||
|
||||
/// Make SimpleComplete a global variable.
|
||||
window.SimpleComplete = SimpleComplete;
|
||||
|
||||
export default SimpleComplete;
|
||||
export { SimpleComplete };
|
||||
37
src/simplecomplete/options.js
Normal file
37
src/simplecomplete/options.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const defaultOptions = {
|
||||
url: null,
|
||||
method: 'GET',
|
||||
headers: null,
|
||||
delay: 300,
|
||||
timeout: 30,
|
||||
multiple: false,
|
||||
paramName: 'q',
|
||||
debug: false,
|
||||
minLength: null,
|
||||
placeholder: 'Search',
|
||||
blankable: false,
|
||||
|
||||
// ui stuff and options/settings
|
||||
params () {},
|
||||
init () {},
|
||||
error () {},
|
||||
|
||||
// user interaction
|
||||
focus () {}, // user clicks input
|
||||
blur () {}, // user leaves input
|
||||
onInput () {}, // user starts input
|
||||
keydown () {}, // user presses a key
|
||||
keyup () {}, // user releases a key
|
||||
select () {}, // user clicks an element
|
||||
reset () {}, // user resets the element to its initial state
|
||||
|
||||
// data handling
|
||||
onBeforeRequest () {}, // before the request to the url is sent
|
||||
onRequest () {},
|
||||
onResponse () {}, // server sends a response
|
||||
onErrorResponse () {}, // server error
|
||||
onParseItem () {}, // parses an item from response
|
||||
onRenderItem (out, data) { return out }, // renders an item from response
|
||||
}
|
||||
|
||||
export default defaultOptions
|
||||
515
src/simplecomplete/simplecomplete.js
Normal file
515
src/simplecomplete/simplecomplete.js
Normal file
@@ -0,0 +1,515 @@
|
||||
import contentloaded from './utils/contentloaded.js'
|
||||
import _defaults from './options.js'
|
||||
|
||||
export default class SimpleComplete {
|
||||
static KEYS = {
|
||||
ENTER: 13,
|
||||
TAB: 9
|
||||
}
|
||||
static initClass () {
|
||||
this.prototype.events = []
|
||||
|
||||
window.contentloaded = contentloaded
|
||||
}
|
||||
|
||||
constructor (el, options = null) {
|
||||
this.results = null
|
||||
this.query = null
|
||||
this.launchedRequest = null
|
||||
this.options = {
|
||||
..._defaults,
|
||||
...(options || {})
|
||||
}
|
||||
|
||||
if ('string' === typeof el) {
|
||||
let els = document.querySelectorAll(el)
|
||||
if (els && els.length > 0) {
|
||||
els.forEach(el => {
|
||||
if (!SimpleComplete.instances) {
|
||||
SimpleComplete.instances = []
|
||||
}
|
||||
SimpleComplete.instances.push(new SimpleComplete(el, options))
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.element = el
|
||||
|
||||
if (this.element.simplecomplete) {
|
||||
throw new Error('nope')
|
||||
}
|
||||
|
||||
this.element.simplecomplete = this
|
||||
this.hideOrigin ()
|
||||
this.initDOM ()
|
||||
this.initEvents ()
|
||||
|
||||
this.updateLabel ()
|
||||
}
|
||||
}
|
||||
|
||||
hideOrigin () {
|
||||
if(!this.options.debug) {
|
||||
this.element.style.width = '1px';
|
||||
this.element.style.height = '1px';
|
||||
this.element.style.position = 'absolute'
|
||||
this.element.style.left = `${-window.innerWidth * 99}px`
|
||||
this.element.style.top = '0'
|
||||
} else {
|
||||
this.element.style.border = 'solid 3px red'
|
||||
this.element.style.marginTop = '1em'
|
||||
this.element.style.marginBottom = '1em'
|
||||
}
|
||||
}
|
||||
|
||||
initDOM () {
|
||||
this.parentNode = this.element.parentNode
|
||||
|
||||
this.wrapper = SimpleComplete.createElement('DIV', {
|
||||
classes: 'simplecomplete'
|
||||
})
|
||||
|
||||
if (this.options.blankable) {
|
||||
this.wrapper.classList.add('is-blankable')
|
||||
}
|
||||
|
||||
if (this.isMultiple()) {
|
||||
this.wrapper.classList.add('is-multiple')
|
||||
}
|
||||
|
||||
if (this.isSelectElement()) {
|
||||
this.wrapper.classList.add('is-select')
|
||||
}
|
||||
|
||||
let inputWrapper = SimpleComplete.createElement('DIV', {
|
||||
classes: 'simplecomplete__input'
|
||||
})
|
||||
|
||||
this.fakeInput = SimpleComplete.createElement('DIV', {
|
||||
classes: 'simplecomplete__input-field'
|
||||
})
|
||||
this.cancelInputBtn = SimpleComplete.createElement('A', {
|
||||
classes: 'simplecomplete__input-cancel'
|
||||
})
|
||||
|
||||
this.cancelInputBtn.addEventListener('click', e => {
|
||||
e.preventDefault ()
|
||||
e.stopPropagation ()
|
||||
document.body.dispatchEvent(new Event('click'))
|
||||
})
|
||||
|
||||
inputWrapper.appendChild(this.fakeInput)
|
||||
inputWrapper.appendChild(this.cancelInputBtn)
|
||||
|
||||
this.currentValue = SimpleComplete.createElement('DIV', {
|
||||
classes: 'simplecomplete__value'
|
||||
})
|
||||
|
||||
this.wrapper.appendChild(this.currentValue)
|
||||
this.wrapper.appendChild(inputWrapper)
|
||||
|
||||
// render results
|
||||
let resultsWrapper = SimpleComplete.createElement('DIV', {
|
||||
classes: 'simplecomplete__results'
|
||||
})
|
||||
|
||||
this.resultsList = SimpleComplete.createElement('DIV', {
|
||||
classes: 'simplecomplete__results-list'
|
||||
})
|
||||
|
||||
resultsWrapper.appendChild(this.resultsList)
|
||||
this.wrapper.appendChild(resultsWrapper)
|
||||
|
||||
// insert wrapper into parentnode of element
|
||||
this.element.parentNode.insertBefore(this.wrapper, this.element.nextSibling)
|
||||
|
||||
this.onResults(Array.from(this.element.options || []))
|
||||
}
|
||||
|
||||
initEvents () {
|
||||
this.element.addEventListener('focus', e => {
|
||||
e.preventDefault ()
|
||||
this.onFocus ()
|
||||
})
|
||||
this.wrapper.addEventListener('click', this.onFocus.bind(this))
|
||||
this.currentValue.addEventListener('click', this.onFocus.bind(this))
|
||||
|
||||
this.fakeInput.addEventListener('keydown', e => {
|
||||
switch (e.keyCode) {
|
||||
case SimpleComplete.KEYS.TAB:
|
||||
case SimpleComplete.KEYS.ENTER:
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
})
|
||||
this.fakeInput.addEventListener('keyup', this.onInput.bind(this))
|
||||
}
|
||||
|
||||
onInput (e) {
|
||||
if (this.launchedRequest) {
|
||||
window.clearTimeout(this.launchedRequest)
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case SimpleComplete.KEYS.ENTER:
|
||||
this.onSend()
|
||||
case SimpleComplete.KEYS.TAB:
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
this.query = e.target.textContent.trim()
|
||||
this.launchedRequest = window.setTimeout(this.onSend.bind(this), this.options.delay || 300)
|
||||
this.onResults()
|
||||
}
|
||||
|
||||
onSend (e = null) {
|
||||
if (!this.query || (this.options.minLength > 0 && this.query.length < this.options.minLength)) {
|
||||
this.onError('simplecomplete.errors.min-length')
|
||||
} else {
|
||||
if (this.wrapper.classList.contains('is-busy')) {
|
||||
this.queued = true
|
||||
} else {
|
||||
console.log('TODO: send request')
|
||||
this.wrapper.classList.add('is-busy')
|
||||
window.setTimeout(() => {
|
||||
this.wrapper.classList.remove('is-busy')
|
||||
if(this.queued) {
|
||||
this.queued = false
|
||||
this.onSend ()
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isMultiple () {
|
||||
return this.element.multiple || false
|
||||
}
|
||||
|
||||
deselectOption (_val) {
|
||||
let _opts = Array.from(this.element.selectedOptions)
|
||||
|
||||
for (let _key in _opts) {
|
||||
let o = _opts[_key]
|
||||
if (o.value == _val) {
|
||||
o.selected = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectOption (_val) {
|
||||
let _opts = Array.from(this.element.options)
|
||||
|
||||
for (let _key in _opts) {
|
||||
let o = _opts[_key]
|
||||
if (o.value == _val) {
|
||||
o.selected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateValue (_val = null) {
|
||||
if (_val !== null || this.options.blankable) {
|
||||
if (this.isMultiple()) {
|
||||
if (this.isSelected(_val)) {
|
||||
this.deselectOption (_val)
|
||||
} else {
|
||||
this.selectOption (_val)
|
||||
}
|
||||
} else {
|
||||
this.element.value = _val
|
||||
}
|
||||
|
||||
}
|
||||
this.updateLabel ()
|
||||
this.onResults ()
|
||||
}
|
||||
|
||||
isSelectElement () {
|
||||
return (this.element.tagName || false) && 'SELECT' == this.element.tagName
|
||||
}
|
||||
|
||||
updateLabel () {
|
||||
this.currentValue.classList.remove('is-empty')
|
||||
this.currentValue.innerHTML = ''
|
||||
if (this.isMultiple() || this.isSelectElement()) {
|
||||
let _opts = Array.from(this.element.selectedOptions)
|
||||
for (let _key in _opts) {
|
||||
let o = _opts[_key]
|
||||
|
||||
let badge = SimpleComplete.createElement('SPAN', {
|
||||
classes: 'selected-option'
|
||||
})
|
||||
|
||||
badge.textContent = o.textContent
|
||||
badge.dataset.value = o.value
|
||||
if (this.isMultiple() || this.options.blankable) {
|
||||
badge.addEventListener('click', e => {
|
||||
e.preventDefault ()
|
||||
e.stopPropagation ()
|
||||
this.deselectOption(e.target.dataset.value)
|
||||
this.updateValue()
|
||||
})
|
||||
}
|
||||
|
||||
this.currentValue.appendChild(badge)
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.currentValue.childNodes.length) {
|
||||
let placeholder = this.element.value || this.element.getAttribute('placeholder') || this.element.dataset.placeholder || this.options.placeholder || 'Search'
|
||||
if (this.isSelectElement()) {
|
||||
let badge = SimpleComplete.createElement('SPAN', {
|
||||
classes: 'empty-item'
|
||||
})
|
||||
|
||||
badge.textContent = placeholder
|
||||
|
||||
this.currentValue.appendChild(badge)
|
||||
} else {
|
||||
this.currentValue.textContent = placeholder
|
||||
}
|
||||
|
||||
if (placeholder == this.options.placeholder) {
|
||||
this.currentValue.classList.add('is-empty')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onError (key) {
|
||||
console.log(key)
|
||||
}
|
||||
|
||||
onFocus (e) {
|
||||
if (e) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
document.body.dispatchEvent(new Event('click'))
|
||||
this.wrapper.classList.add('active')
|
||||
if (this.results.length > 0) {
|
||||
this.wrapper.classList.add('has-results')
|
||||
}
|
||||
this.currentValue.classList.add('hide')
|
||||
this.fakeInput.setAttribute('contenteditable', true)
|
||||
this.fakeInput.focus()
|
||||
|
||||
this.options.focus()
|
||||
|
||||
document.body.addEventListener('click', e => {
|
||||
this.onBlur()
|
||||
}, {once:true})
|
||||
}
|
||||
|
||||
onBlur () {
|
||||
this.query = null
|
||||
this.fakeInput.textContent = ''
|
||||
this.wrapper.classList.remove('active')
|
||||
this.currentValue.classList.remove('hide')
|
||||
this.fakeInput.setAttribute('contenteditable', false)
|
||||
|
||||
this.wrapper.classList.remove('has-results')
|
||||
|
||||
this.onResults ()
|
||||
|
||||
this.options.blur()
|
||||
}
|
||||
|
||||
onSelect (_val) {
|
||||
this.updateValue (_val)
|
||||
document.body.dispatchEvent(new Event('click'))
|
||||
}
|
||||
|
||||
onResults (data = null) {
|
||||
if(data !== null) {
|
||||
this.results = data
|
||||
}
|
||||
|
||||
if(this.results && this.resultsList) {
|
||||
this.resultsList.innerHTML = ''
|
||||
|
||||
try {
|
||||
this.results.filter(res => {
|
||||
return !this.query || res.value.toString().toLowerCase().indexOf(this.query.toLowerCase()) >= 0 || res.label.toLowerCase().indexOf(this.query.toLowerCase()) >= 0
|
||||
}).forEach(res => {
|
||||
this.resultsList.appendChild(this.renderOption(res))
|
||||
})
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
if (!this.resultsList.childNodes.length) {
|
||||
let emptyNode = SimpleComplete.createElement('DIV', {
|
||||
classes: 'simplecomplete__results-list-empty'
|
||||
})
|
||||
|
||||
emptyNode.textContent = this.options.emptyText || 'No results'
|
||||
|
||||
this.resultsList.appendChild(emptyNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isSelected (_val) {
|
||||
let opts = Array.from(this.element.selectedOptions || [])
|
||||
for (let _index = 0; _index < opts.length; _index++) {
|
||||
let option = opts[_index]
|
||||
if (option.value == _val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return this.element.value == _val || false
|
||||
}
|
||||
|
||||
renderOption (data) {
|
||||
let _out = SimpleComplete.createElement('A', {
|
||||
classes: 'simplecomplete__results-list-item'
|
||||
})
|
||||
|
||||
if (this.isSelected(data.value)) {
|
||||
_out.classList.add('selected')
|
||||
}
|
||||
|
||||
if (data.disabled) {
|
||||
_out.classList.add('disabled')
|
||||
}
|
||||
|
||||
if (!this.isSelected(data.value) || this.isMultiple()) {
|
||||
_out.addEventListener('click', e => {
|
||||
e.preventDefault ()
|
||||
e.stopPropagation ()
|
||||
if (!data.disabled) {
|
||||
this.onSelect(data.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// cut the label and mark the search term in the label if the search term is set
|
||||
if (!!this.query) {
|
||||
let regexp = new RegExp(this.query,'gi')
|
||||
let label = data.label
|
||||
|
||||
let check = label.matchAll(regexp)
|
||||
|
||||
if(check) {
|
||||
let hightlightSpan = SimpleComplete.createElement('SPAN', {
|
||||
classes: 'highlight'
|
||||
});
|
||||
let offset = 0;
|
||||
[...check].forEach(res => {
|
||||
hightlightSpan.textContent = res[0]
|
||||
let parts = [
|
||||
label.substr(0, res.index),
|
||||
hightlightSpan.outerHTML,
|
||||
label.substr(offset + res.index + res[0].length, label.length + offset)
|
||||
]
|
||||
|
||||
offset += hightlightSpan.outerHTML.length
|
||||
|
||||
label = parts.join('')
|
||||
})
|
||||
}
|
||||
_out.innerHTML = label
|
||||
} else {
|
||||
_out.textContent = data.label
|
||||
}
|
||||
|
||||
try {
|
||||
return this.options.onRenderItem(_out, data)
|
||||
} catch(err) {
|
||||
return _out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SimpleComplete.initClass ()
|
||||
|
||||
SimpleComplete.version = 'dev'
|
||||
|
||||
SimpleComplete.options = {}
|
||||
|
||||
SimpleComplete.instances = []
|
||||
|
||||
SimpleComplete.autoDiscover = true
|
||||
|
||||
SimpleComplete.discover = () => {
|
||||
let inputs = document.querySelectorAll('[data-sc]')
|
||||
let res = []
|
||||
|
||||
if (inputs && inputs.length > 0) {
|
||||
inputs.forEach(_in => {
|
||||
console.log(_in, _in.dataset.blankable || false)
|
||||
let options = {
|
||||
minLength: _in.dataset.minLength || null,
|
||||
blankable: _in.dataset.blankable || false
|
||||
}
|
||||
res.push(new SimpleComplete(_in, options));
|
||||
})
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
SimpleComplete.createElement = function (tag) {
|
||||
let options = arguments[1] || {}
|
||||
options = {
|
||||
id: null,
|
||||
classes: null,
|
||||
attributes: null,
|
||||
styles: null,
|
||||
data: null,
|
||||
...options
|
||||
}
|
||||
|
||||
let el = document.createElement(tag)
|
||||
|
||||
if (el) {
|
||||
if (options.styles) {
|
||||
el.styles = options.styles
|
||||
}
|
||||
if (options.classes) {
|
||||
if ('string' == typeof options.classes) {
|
||||
options.classes = options.classes.split(' ')
|
||||
}
|
||||
options.classes.forEach(_class => el.classList.add(_class))
|
||||
}
|
||||
|
||||
if (options.attributes) {
|
||||
Object.keys(options.attributes).forEach(_key => el.setAttribute(_key, options.attributes[_key]))
|
||||
}
|
||||
|
||||
if (options.data) {
|
||||
Object.keys(options.data).forEach(_key => {
|
||||
el.dataset[_key] = options.data[_key]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return el
|
||||
}
|
||||
|
||||
SimpleComplete.getElement = (el, name) => {
|
||||
let $el = null
|
||||
|
||||
if (document.querySelectorAll) {
|
||||
$el = document.querySelector(el)
|
||||
} else if(!!el.nodeType) {
|
||||
$el = el
|
||||
}
|
||||
|
||||
if (!$el) {
|
||||
throw new Error('')
|
||||
}
|
||||
|
||||
return $el
|
||||
}
|
||||
|
||||
SimpleComplete.__autoDiscoverFunc = () => {
|
||||
if (SimpleComplete.autoDiscover) {
|
||||
return SimpleComplete.discover()
|
||||
}
|
||||
}
|
||||
|
||||
contentloaded(window, SimpleComplete.__autoDiscoverFunc)
|
||||
|
||||
export { SimpleComplete }
|
||||
56
src/simplecomplete/utils/contentloaded.js
Normal file
56
src/simplecomplete/utils/contentloaded.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* contentloaded.js
|
||||
*
|
||||
* Author: Diego Perini (diego.perini at gmail.com)
|
||||
* Summary: cross-browser wrapper for DOMContentLoaded
|
||||
* Updated: 20101020
|
||||
* License: MIT
|
||||
* Version: 1.2
|
||||
*
|
||||
* URL:
|
||||
* http://javascript.nwbox.com/ContentLoaded/
|
||||
* http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
|
||||
*/
|
||||
|
||||
export default function contentLoaded (win, fn) {
|
||||
let done = false;
|
||||
let top = true;
|
||||
let doc = win.document;
|
||||
let root = doc.documentElement;
|
||||
let add = doc.addEventListener ? "addEventListener" : "attachEvent";
|
||||
let rem = doc.addEventListener ? "removeEventListener" : "detachEvent";
|
||||
let pre = doc.addEventListener ? "" : "on";
|
||||
var init = function (e) {
|
||||
if (e.type === "readystatechange" && doc.readyState !== "complete") {
|
||||
return;
|
||||
}
|
||||
(e.type === "load" ? win : doc)[rem](pre + e.type, init, false);
|
||||
if (!done && (done = true)) {
|
||||
return fn.call(win, e.type || e);
|
||||
}
|
||||
};
|
||||
|
||||
var poll = function () {
|
||||
try {
|
||||
root.doScroll("left");
|
||||
} catch (e) {
|
||||
setTimeout(poll, 50);
|
||||
return;
|
||||
}
|
||||
return init("poll");
|
||||
};
|
||||
|
||||
if (doc.readyState !== "complete") {
|
||||
if (doc.createEventObject && root.doScroll) {
|
||||
try {
|
||||
top = !win.frameElement;
|
||||
} catch (error) {}
|
||||
if (top) {
|
||||
poll();
|
||||
}
|
||||
}
|
||||
doc[add](pre + "DOMContentLoaded", init, false);
|
||||
doc[add](pre + "readystatechange", init, false);
|
||||
return win[add](pre + "load", init, false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user