2020-02-16 18:30:00 +00:00
|
|
|
import Vue from 'vue'
|
2020-10-19 15:32:53 +00:00
|
|
|
import FtTooltip from '../ft-tooltip/ft-tooltip.vue'
|
2021-10-01 07:38:33 +00:00
|
|
|
import { mapActions } from 'vuex'
|
2020-02-16 18:30:00 +00:00
|
|
|
|
|
|
|
export default Vue.extend({
|
|
|
|
name: 'FtInput',
|
2020-10-19 15:32:53 +00:00
|
|
|
components: {
|
|
|
|
'ft-tooltip': FtTooltip
|
|
|
|
},
|
2020-02-16 18:30:00 +00:00
|
|
|
props: {
|
|
|
|
placeholder: {
|
|
|
|
type: String,
|
|
|
|
required: true
|
|
|
|
},
|
2020-06-02 02:42:29 +00:00
|
|
|
value: {
|
|
|
|
type: String,
|
|
|
|
default: ''
|
|
|
|
},
|
2021-10-01 07:38:33 +00:00
|
|
|
showActionButton: {
|
2020-02-16 18:30:00 +00:00
|
|
|
type: Boolean,
|
|
|
|
default: true
|
2020-03-01 03:37:02 +00:00
|
|
|
},
|
2021-09-06 10:09:11 +00:00
|
|
|
showClearTextButton: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false
|
|
|
|
},
|
2020-06-02 02:42:29 +00:00
|
|
|
showLabel: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false
|
|
|
|
},
|
2020-03-01 03:37:02 +00:00
|
|
|
isSearch: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false
|
2020-06-02 02:42:29 +00:00
|
|
|
},
|
2021-04-15 19:30:26 +00:00
|
|
|
selectOnFocus: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false
|
|
|
|
},
|
2020-08-24 02:56:33 +00:00
|
|
|
disabled: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false
|
|
|
|
},
|
2021-06-15 14:42:37 +00:00
|
|
|
spellcheck: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true
|
|
|
|
},
|
2020-06-02 02:42:29 +00:00
|
|
|
dataList: {
|
|
|
|
type: Array,
|
|
|
|
default: () => { return [] }
|
2020-10-19 15:32:53 +00:00
|
|
|
},
|
|
|
|
tooltip: {
|
|
|
|
type: String,
|
|
|
|
default: ''
|
2020-08-05 02:18:39 +00:00
|
|
|
}
|
2020-02-16 18:30:00 +00:00
|
|
|
},
|
|
|
|
data: function () {
|
|
|
|
return {
|
|
|
|
id: '',
|
2020-12-11 18:14:11 +00:00
|
|
|
inputData: '',
|
|
|
|
searchState: {
|
|
|
|
showOptions: false,
|
|
|
|
selectedOption: -1,
|
|
|
|
isPointerInList: false
|
2021-09-23 06:45:14 +00:00
|
|
|
},
|
2021-10-05 05:58:35 +00:00
|
|
|
visibleDataList: this.dataList,
|
2021-09-23 06:45:14 +00:00
|
|
|
// This button should be invisible on app start
|
|
|
|
// As the text input box should be empty
|
2021-10-01 07:38:33 +00:00
|
|
|
clearTextButtonExisting: false,
|
|
|
|
clearTextButtonVisible: false,
|
|
|
|
actionButtonIconName: 'search'
|
2020-03-01 03:37:02 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
barColor: function () {
|
|
|
|
return this.$store.getters.getBarColor
|
|
|
|
},
|
|
|
|
|
|
|
|
forceTextColor: function () {
|
|
|
|
return this.isSearch && this.barColor
|
2020-06-02 02:42:29 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
idDataList: function () {
|
|
|
|
return `${this.id}_datalist`
|
2021-09-23 06:45:14 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
inputDataPresent: function () {
|
|
|
|
return this.inputData.length > 0
|
2020-02-16 18:30:00 +00:00
|
|
|
}
|
|
|
|
},
|
2020-08-24 02:56:33 +00:00
|
|
|
watch: {
|
2021-09-23 06:45:14 +00:00
|
|
|
inputDataPresent: function (newVal, oldVal) {
|
|
|
|
if (newVal) {
|
|
|
|
// The button needs to be visible **immediately**
|
|
|
|
// To allow user to see the transition
|
2021-10-01 07:38:33 +00:00
|
|
|
this.clearTextButtonExisting = true
|
|
|
|
// The transition is not rendered if this property is set right after
|
|
|
|
// It's visible
|
|
|
|
setTimeout(() => {
|
|
|
|
this.clearTextButtonVisible = true
|
|
|
|
}, 0)
|
2021-09-23 06:45:14 +00:00
|
|
|
} else {
|
2021-10-01 07:38:33 +00:00
|
|
|
// Hide the button with transition
|
|
|
|
this.clearTextButtonVisible = false
|
|
|
|
// Remove the button after the transition
|
2021-09-23 06:45:14 +00:00
|
|
|
// 0.2s in CSS = 200ms in JS
|
|
|
|
setTimeout(() => {
|
2021-10-01 07:38:33 +00:00
|
|
|
this.clearTextButtonExisting = false
|
2021-09-23 06:45:14 +00:00
|
|
|
}, 200)
|
|
|
|
}
|
2020-08-24 02:56:33 +00:00
|
|
|
}
|
|
|
|
},
|
2020-02-16 18:30:00 +00:00
|
|
|
mounted: function () {
|
|
|
|
this.id = this._uid
|
2020-06-19 19:58:59 +00:00
|
|
|
this.inputData = this.value
|
2021-10-05 05:58:35 +00:00
|
|
|
this.updateVisibleDataList()
|
2020-02-16 18:30:00 +00:00
|
|
|
|
|
|
|
setTimeout(this.addListener, 200)
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
handleClick: function () {
|
2021-10-01 07:38:33 +00:00
|
|
|
// No action if no input text
|
|
|
|
if (!this.inputDataPresent) { return }
|
|
|
|
|
2020-12-11 18:14:11 +00:00
|
|
|
this.searchState.showOptions = false
|
|
|
|
this.$emit('input', this.inputData)
|
2020-02-16 18:30:00 +00:00
|
|
|
this.$emit('click', this.inputData)
|
|
|
|
},
|
|
|
|
|
2021-10-05 05:58:35 +00:00
|
|
|
handleInput: function (val) {
|
2020-12-11 18:14:11 +00:00
|
|
|
if (this.isSearch &&
|
|
|
|
this.searchState.selectedOption !== -1 &&
|
2021-10-05 05:58:35 +00:00
|
|
|
this.inputData === this.visibleDataList[this.searchState.selectedOption]) { return }
|
2021-10-01 07:38:33 +00:00
|
|
|
this.handleActionIconChange()
|
2021-10-05 05:58:35 +00:00
|
|
|
this.updateVisibleDataList()
|
|
|
|
this.$emit('input', val)
|
2020-06-02 02:42:29 +00:00
|
|
|
},
|
|
|
|
|
2021-09-06 10:09:11 +00:00
|
|
|
handleClearTextClick: function () {
|
|
|
|
this.inputData = ''
|
2021-10-01 07:38:33 +00:00
|
|
|
this.handleActionIconChange()
|
2021-10-05 05:58:35 +00:00
|
|
|
this.updateVisibleDataList()
|
2021-09-06 10:09:11 +00:00
|
|
|
this.$emit('input', this.inputData)
|
2021-09-23 06:45:14 +00:00
|
|
|
|
|
|
|
// Focus on input element after text is clear for better UX
|
|
|
|
const inputElement = document.getElementById(this.id)
|
|
|
|
inputElement.focus()
|
2021-09-06 10:09:11 +00:00
|
|
|
},
|
|
|
|
|
2021-10-01 07:38:33 +00:00
|
|
|
handleActionIconChange: function() {
|
|
|
|
// Only need to update icon if visible
|
|
|
|
if (!this.showActionButton) { return }
|
|
|
|
|
|
|
|
if (!this.inputDataPresent) {
|
|
|
|
// Change back to default icon if text is blank
|
|
|
|
this.actionButtonIconName = 'search'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update action button icon according to input
|
|
|
|
try {
|
|
|
|
this.getYoutubeUrlInfo(this.inputData).then((result) => {
|
|
|
|
let isYoutubeLink = false
|
|
|
|
|
|
|
|
switch (result.urlType) {
|
|
|
|
case 'video':
|
|
|
|
case 'playlist':
|
|
|
|
case 'search':
|
|
|
|
case 'channel':
|
|
|
|
isYoutubeLink = true
|
|
|
|
break
|
|
|
|
case 'hashtag':
|
|
|
|
// TODO: Implement a hashtag related view
|
|
|
|
// isYoutubeLink is already `false`
|
|
|
|
break
|
|
|
|
|
|
|
|
case 'invalid_url':
|
|
|
|
default: {
|
|
|
|
// isYoutubeLink is already `false`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isYoutubeLink) {
|
|
|
|
// Go to URL (i.e. Video/Playlist/Channel
|
|
|
|
this.actionButtonIconName = 'arrow-right'
|
|
|
|
} else {
|
|
|
|
// Search with text
|
|
|
|
this.actionButtonIconName = 'search'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
} catch (ex) {
|
|
|
|
// On exception, consider text as invalid URL
|
|
|
|
this.actionButtonIconName = 'search'
|
|
|
|
// Rethrow exception
|
|
|
|
throw ex
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-02-16 18:30:00 +00:00
|
|
|
addListener: function () {
|
|
|
|
const inputElement = document.getElementById(this.id)
|
|
|
|
|
|
|
|
if (inputElement !== null) {
|
|
|
|
inputElement.addEventListener('keydown', (event) => {
|
|
|
|
if (event.keyCode === 13) {
|
|
|
|
this.handleClick()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2020-12-11 18:14:11 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
handleOptionClick: function (index) {
|
|
|
|
this.searchState.showOptions = false
|
2021-10-05 05:58:35 +00:00
|
|
|
this.inputData = this.visibleDataList[index]
|
2020-12-11 18:14:11 +00:00
|
|
|
this.$emit('input', this.inputData)
|
|
|
|
this.handleClick()
|
|
|
|
},
|
|
|
|
|
|
|
|
handleKeyDown: function (keyCode) {
|
|
|
|
if (this.dataList.length === 0) { return }
|
|
|
|
// Update selectedOption based on arrow key pressed
|
|
|
|
if (keyCode === 40) {
|
|
|
|
this.searchState.selectedOption = (this.searchState.selectedOption + 1) % this.dataList.length
|
|
|
|
} else if (keyCode === 38) {
|
|
|
|
if (this.searchState.selectedOption === -1) {
|
|
|
|
this.searchState.selectedOption = this.dataList.length - 1
|
|
|
|
} else {
|
|
|
|
this.searchState.selectedOption--
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.searchState.selectedOption = -1
|
|
|
|
}
|
|
|
|
|
2021-10-05 05:58:35 +00:00
|
|
|
// Key pressed isn't enter
|
|
|
|
if (keyCode !== 13) {
|
|
|
|
this.searchState.showOptions = true
|
|
|
|
}
|
2020-12-11 18:14:11 +00:00
|
|
|
// Update Input box value if arrow keys were pressed
|
2021-10-05 05:58:35 +00:00
|
|
|
if ((keyCode === 40 || keyCode === 38) && this.searchState.selectedOption !== -1) {
|
|
|
|
this.inputData = this.visibleDataList[this.searchState.selectedOption]
|
|
|
|
} else {
|
|
|
|
this.updateVisibleDataList()
|
|
|
|
}
|
2020-12-11 18:14:11 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
handleInputBlur: function () {
|
|
|
|
if (!this.searchState.isPointerInList) { this.searchState.showOptions = false }
|
2021-04-15 19:30:26 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
handleFocus: function(e) {
|
|
|
|
this.searchState.showOptions = true
|
|
|
|
if (this.selectOnFocus) {
|
|
|
|
e.target.select()
|
|
|
|
}
|
2021-10-01 07:38:33 +00:00
|
|
|
},
|
|
|
|
|
2021-10-05 05:58:35 +00:00
|
|
|
updateVisibleDataList: function () {
|
|
|
|
if (this.dataList.length === 0) { return }
|
|
|
|
if (this.inputData === '') {
|
|
|
|
this.visibleDataList = this.dataList
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// get list of items that match input
|
|
|
|
const visList = this.dataList.filter(x => {
|
|
|
|
if (x.toLowerCase().indexOf(this.inputData.toLowerCase()) !== -1) {
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
this.visibleDataList = visList
|
|
|
|
},
|
|
|
|
|
2021-10-01 07:38:33 +00:00
|
|
|
...mapActions([
|
|
|
|
'getYoutubeUrlInfo'
|
|
|
|
])
|
2020-02-16 18:30:00 +00:00
|
|
|
}
|
|
|
|
})
|