<template>
<div class="ion-searchbar" :class="[
modeClass,colorClass,
{'searchbar-has-focus':sbHasFocus},
{'searchbar-has-value':theValue},
{'searchbar-animated':shouldAnimated},
{'searchbar-active':sbHasFocus},
{'searchbar-show-cancel':showCancelButton},
{'searchbar-left-aligned':shouldAlignLeft}
]">
<div class="searchbar-input-container" @touchstart="setFocus">
<!--在md模式下,md的取消按钮是在这里的,当点击inputs输入时,返回按钮将覆盖search按钮-->
<vm-button mode="md" @click.native="cancelSearchbar($event)" clear color="dark" class="searchbar-md-cancel" role="button">
<vm-icon mode="md" name="md-arrow-back"></vm-icon>
</vm-button>
<!--input左边的search按钮-->
<div ref="searchbarIcon" class="searchbar-search-icon"></div>
<input ref="searchbarInput" class="searchbar-input" id="searchbarInput" @input="onInputHandler($event)" @blur="onBlurHandler($event)" @focus="onFocusHandler($event)" :value="theValue" :placeholder="placeholder" :type="type" :autocomplete="autocompleteValue" :autocorrect="autocorrectValue" :spellcheck="spellcheckValue">
<!--input右边的关闭按钮-->
<vm-button clear class="searchbar-clear-icon" :mode="mode" @click.native="clearInput($event)" role="button"></vm-button>
</div>
<!--取消按钮,点击input时出现,只对IOS,md在search icon位置显示,wp没有-->
<vm-button ref="cancelButton" mode="ios" clear @click.native="cancelSearchbar($event)" class="searchbar-ios-cancel" role="button">
{{cancelButtonText}}
</vm-button>
</div>
</template>
<script type="text/javascript">
import { isNumber, isBoolean } from '../../util/util'
import ModeMixins from '../../themes/theme.mixins'
import VmButton from '../button'
import VmIcon from '../icon'
export default {
name: 'vm-searchbar',
mixins: [ModeMixins],
components: {
VmButton,
VmIcon
},
data () {
return {
isCancelVisible: false,
sbHasFocus: false,
shouldAlignLeft: true,
shouldBlur: true,
shouldAnimated: false,
// 三个元素的id的document实例
searchbarIconElement: '',
searchbarInputElement: '',
cancelButtonElement: '',
// 外部的value映射
theValue: this.value,
timer: '',
placeHolderTextWidth: null // number eg: 44
}
},
props: {
/**
* Set the the cancel button text. Default: "Cancel".
*/
cancelButtonText: {
type: String,
default: 'Cancel'
},
/**
* Whether to show the cancel button or not. Default: "false".
*/
showCancelButton: [Boolean],
/**
* How long, in milliseconds, to wait to trigger the onInput event after each keystroke. Default 250.
*/
debounce: {
type: Number,
default: 0
},
/**
* Set the input's placeholder. Default "Search".
*/
placeholder: {
type: String,
default: 'Search'
},
/**
* Set the input's autocomplete property. Values: "on", "off". Default "off".
*/
autocomplete: {
type: String,
default: 'off'
},
/**
* Set the input's autocorrect property. Values: "on", "off". Default "off".
*/
autocorrect: {
type: String,
default: 'off'
},
autofocus: [Boolean, Number],
/**
* Set the input's spellcheck property. Values: true, false. Default false.
*/
spellcheck: {
type: [String, Boolean],
default: false
},
/**
* Set the type of the input. Values: "text", "password", "email", "number", "search", "tel", "url". Default "search".
*/
type: {
type: String,
default: 'search'
},
/**
* Configures if the searchbar is animated or no. By default, animation is false.
*/
animated: {
type: Boolean,
default: false
},
/**
* Set the input value.
*/
value: String
},
watch: {
value (val) {
this.theValue = val
this.positionElements()
}
},
computed: {
// props处理
autocompleteValue () {
return this.autocomplete === '' || this.autocomplete === 'on'
? 'on'
: 'off'
},
autocorrectValue () {
return this.autocorrect === '' || this.autocorrect === 'on'
? 'on'
: 'off'
},
spellcheckValue () {
return (
this.spellcheck === '' ||
this.spellcheck === 'true' ||
this.spellcheck === true
)
}
},
methods: {
// -------- public --------
/**
* @function setFocus
* @description
* 手动设置当前input的focus状态
*/
setFocus () {
this.searchbarInputElement.focus()
},
// -------- private --------
/**
* Update the Searchbar input value when the input changes
* @private
*/
onInputHandler ($event) {
let _valueInner = $event.target ? $event.target.value : ''
if (_valueInner) {
this.theValue = _valueInner
} else {
this.theValue = null
}
if (this.debounce > 16) {
window.clearTimeout(this.timer)
this.timer = window.setTimeout(() => {
// 通知父组件的v-model
this.$emit('input', this.theValue)
this.$emit('onInput', $event)
}, this.debounce)
} else {
/**
* @event component:Searchbar#onInput
* @description input事件
* @property {object} $event - 事件对象
*/
this.$emit('input', this.theValue)
this.$emit('onInput', $event)
}
},
/**
* Sets the Searchbar to focused and active on input focus.
* @private
*/
onFocusHandler ($event) {
/**
* @event component:Searchbar#onFocus
* @description focus事件
* @property {object} $event - 事件对象
*/
this.$emit('onFocus', $event)
this.sbHasFocus = true
this.positionElements()
},
/**
* Sets the Searchbar to not focused and checks if it should align left
* based on whether there is a value in the searchbar or not.
* @private
*/
onBlurHandler ($event) {
// shouldBlur: 是否真正的blur, 因为当点击clearBtn时, 需要再次focus, 所以等到16*4ms后, 判断是否blue
// shouldBlur determines if it should blur
// if we are clearing the input we still want to stay focused in the input
// wait for DOM update, because of focus method
window.setTimeout(() => {
if (!this.shouldBlur) {
this.sbHasFocus = true
this.searchbarInputElement.focus()
} else {
/**
* @event component:Searchbar#onBlur
* @description blur事件
* @property {object} $event - 事件对象
*/
this.$emit('onBlur', $event)
this.sbHasFocus = false
this.positionElements()
}
this.shouldBlur = true
}, 16 * 4)
},
/**
* Clears the input field and triggers the control change.
* @private
*/
clearInput ($event) {
this.searchbarInputElement.focus()
/**
* @event component:Searchbar#onClear
* @description clear事件
* @property {object} $event - 事件对象
*/
this.$emit('onClear', $event)
this.shouldBlur = false
if (this.theValue) {
this.theValue = null
this.$emit('input', this.theValue)
this.$emit('onInput', $event)
}
},
/**
* Clears the input field and tells the input to blur since
* the clearInput function doesn't want the input to blur
* then calls the custom cancel function if the user passed one in.
* @private
*/
cancelSearchbar ($event) {
/**
* @event component:Searchbar#onCancel
* @description cancel事件
* @property {object} $event - 事件对象
*/
this.$emit('onCancel', $event)
if (this.theValue) {
this.theValue = null
this.$emit('input', this.theValue)
this.$emit('onInput', $event)
}
this.shouldBlur = true
},
/**
* 当focus时, 设置搜索框的icon/placeholder/cancel button的位置 (ios only)
* @private
*/
positionElements () {
let isAnimated = this.animated
let prevAlignLeft = this.shouldAlignLeft
let shouldAlignLeft =
!isAnimated ||
(this.theValue && this.theValue.toString().trim() !== '') ||
this.sbHasFocus === true
this.shouldAlignLeft = shouldAlignLeft
if (this.mode !== 'ios') {
return
}
if (prevAlignLeft !== shouldAlignLeft) {
this.positionPlaceholder()
}
if (isAnimated) {
this.positionCancelButton()
}
this.shouldAnimated = this.animated
},
positionPlaceholder () {
let inputEle = this.searchbarInputElement
let iconEle = this.searchbarIconElement
console.assert(
inputEle,
'The input element is undefined, please check!::<Function>positionPlaceholder():inputEle'
)
console.assert(
iconEle,
'The icon element is undefined, please check!::<Function>positionPlaceholder():iconEle'
)
if (!inputEle || !iconEle) {
return
}
if (this.shouldAlignLeft) {
inputEle.removeAttribute('style')
iconEle.removeAttribute('style')
} else {
if (this.sbHasFocus) {
this.searchbarInputElement.blur()
}
if (this.placeHolderTextWidth === null) {
// Create a dummy span to get the placeholder width
if (!this.placeholder) {
this.placeHolderTextWidth = 0
} else {
let tempSpan = document.createElement('span')
tempSpan.innerHTML = this.placeholder
tempSpan.style.fontSize = window.getComputedStyle(
inputEle
).fontSize
tempSpan.style.display = 'inline'
document.body.appendChild(tempSpan)
// Get the width of the span then remove it
this.placeHolderTextWidth = tempSpan.offsetWidth
tempSpan.remove()
}
}
// Set the input padding left
let inputLeft = 'calc(50% - ' + this.placeHolderTextWidth / 2 + 'px)'
inputEle.style.paddingLeft = inputLeft
let paddingLeft = this.placeHolderTextWidth === 0 ? 14 : 30
// Set the icon margin left
let iconLeft =
'calc(50% - ' + (this.placeHolderTextWidth / 2 + paddingLeft) + 'px)'
iconEle.style.marginLeft = iconLeft
}
},
/**
* Show the iOS Cancel button on focus, hide it offscreen otherwise
* @private
*/
positionCancelButton () {
if (!this.cancelButtonElement) {
return
}
let showShowCancel = this.sbHasFocus
if (showShowCancel !== this.isCancelVisible) {
let cancelStyleEle = this.cancelButtonElement
let cancelStyle = cancelStyleEle.style
this.isCancelVisible = showShowCancel
if (showShowCancel) {
cancelStyle.marginRight = '0'
} else {
let offset = cancelStyleEle.offsetWidth
if (offset > 0) {
cancelStyle.marginRight = -offset + 'px'
}
}
}
}
},
mounted () {
this.searchbarIconElement = this.$refs.searchbarIcon
this.searchbarInputElement = this.$refs.searchbarInput
this.cancelButtonElement = this.$refs.cancelButton.$el
this.positionElements()
if (isBoolean(this.autofocus) && this.autofocus) {
this.setFocus()
}
if (isNumber(this.autofocus) && this.autofocus > 0) {
window.setTimeout(() => {
this.setFocus()
}, this.autofocus)
}
}
}
</script>
<style lang="scss">
@import "searchbar";
@import "searchbar.ios";
@import "searchbar.md";
</style>