Created
November 8, 2016 00:03
-
-
Save AlexandreBonneau/dd170d67e0649c970291f81b150ceab2 to your computer and use it in GitHub Desktop.
Directive cdz-auto-numeric : wrap an autoNumeric-managed input with Vue.js 2.0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//AutoNumeric 2 : https://rawgithub.com/BobKnothe/autoNumeric/master/autoNumeric-2.0/autoNumeric-2.0-BETA.js //FAIL | |
//AutoNumeric 1.9.46 : https://cdnjs.cloudflare.com/ajax/libs/autonumeric/1.9.46/autoNumeric.min.js //WORK | |
//Live example on http://codepen.io/AnotherLinuxUser/pen/VmwvOK | |
let cdzNumericDirective = { | |
//Known problems : | |
//Using __autoNumercic 2.0 BETA__, when using 2 inputs with the same v-model (or when one input updates the value of another, even with different v-models), when you enter a value in one input, both updates well, but when you focus on the second input, the initial value is shown (since the current autoNumeric 2.0 BETA keeps its own `rawValue`). You can solve this by using 1.9.46 for the time being. | |
bind(el, bindings, vnode, oldVnode) { | |
let jQueryElement = $(el); | |
let rawValue; //Hold the unformatted number | |
//Manage the numeric options | |
let autoNumericOptions = bindings.value; | |
if (null === autoNumericOptions || "" === autoNumericOptions || typeof autoNumericOptions === 'undefined') { | |
autoNumericOptions = { aSep: '.', aDec: ',', altDec: '.', aSign: ' €', pSign: 's', mRound: 'U' }; //Default options | |
} | |
//Init the autoNumeric field | |
jQueryElement.autoNumeric('init', autoNumericOptions); | |
//Manage the initial value, if it is set | |
if (el.value !== '' && el.value !== null) { | |
jQueryElement.autoNumeric('set', el.value); | |
updateVModel(); | |
} | |
//Declare the functions | |
function getUnformattedValue() { | |
return Number(jQueryElement.autoNumeric('get')); | |
} | |
function onKeyup(e) { | |
updateVModel(); | |
} | |
function onPaste(e) { | |
e.preventDefault(); | |
let pasteText = e.clipboardData.getData('text/plain'); | |
let selectionStart = el.selectionStart; | |
let selectionEnd = el.selectionEnd; | |
let tempResult; | |
if (selectionStart === selectionEnd) { //No selection, only a caret, insert the pasted text | |
tempResult = insertCharAtPosition(el.value, pasteText, selectionStart); | |
} | |
else { //A user selection is present, replace that with the pasted text | |
let firstPart = el.value.slice(0, selectionStart); | |
let lastPart = el.value.slice(selectionEnd, el.value.length); | |
tempResult = firstPart + pasteText + lastPart; | |
} | |
let finalResult; | |
try { | |
finalResult = unformatEuroNumber(tempResult); | |
} | |
catch (error) { | |
return; | |
} | |
jQueryElement.autoNumeric('set', finalResult); | |
updateVModel(); | |
} | |
function updateVModel() { | |
//Keep the raw unformatted value always up-to-date | |
rawValue = getUnformattedValue(); | |
//Update the v-model value attached to the element, by using the 'input' event (so that other inputs using the same v-model are updated) | |
el.dispatchEvent(new Event('input', { bubbles: true })); | |
console.log(`DOM value [${el.value}], unmasked value [${rawValue}]`); //DEBUG | |
} | |
//Add the event listeners | |
el.addEventListener("keyup", onKeyup, false); | |
el.addEventListener("paste", onPaste, false); | |
}, | |
unbind(el, bindings, vnode, oldVnode) { | |
$(el).autoNumeric('destroy'); | |
el.removeEventListener("keyup", onKeyup, false); | |
el.removeEventListener("paste", onPaste, false); | |
}, | |
}; | |
new Vue({ | |
el : '#app', | |
data() { | |
return { | |
a : 11.11, | |
b : 22.222, | |
c : null, | |
months : 24, | |
} | |
}, | |
computed: { | |
years : { | |
get() { | |
return this.months / 12; | |
}, | |
set(newValue) { | |
this.months = Math.round(newValue * 12); | |
} | |
}, | |
}, | |
directives: { | |
'cdz-numeric' : cdzNumericDirective, | |
} | |
}); | |
//------------------------------------------------------------------------- | |
//-- The following functions are helper function for this specific example, | |
//-- and can be swapped/replaced/deleted as needed. | |
/** | |
* Temporary function to unformat a Euro formatted number. | |
* "1.234.567,89 €" -> 1234567.89 | |
*/ | |
function unformatEuroNumber(strNumber) { | |
//Remove the thousands separators (dots) | |
strNumber = removeDotFromString(strNumber); | |
//Replace the comma by a dot | |
strNumber = replaceDecimalCharByPoint(strNumber, ','); | |
//Remove the currency symbol | |
strNumber = removeEndFromString(strNumber, ' €'); | |
let result = Number(strNumber); | |
if (isNaN(result)) throw new Exception("Impossible to convert to a number."); | |
return result; | |
} | |
function removeStringPartFromString(str, strToRemove) { | |
if (strToRemove === '.') { | |
strToRemove = '\\.'; //Special case for the 'dot' | |
} | |
let regex = new RegExp(strToRemove, 'g'); | |
return str.replace(regex, ""); | |
} | |
function removeDotFromString(str) { | |
let regex = new RegExp('\\.', 'g'); | |
return str.replace(regex, ""); | |
} | |
function removeEndFromString(str, strAtTheEndToRemove) { | |
let regex = new RegExp(strAtTheEndToRemove+'$', 'g'); | |
return str.replace(regex, ""); | |
} | |
function replaceDecimalCharByPoint(val, decimalSeparatorForRegExp) { | |
if (decimalSeparatorForRegExp === '\\.') return val; | |
let regex = new RegExp(decimalSeparatorForRegExp, 'g'); | |
return String(val).replace(regex, '.'); | |
} | |
function isNumber(n) { | |
return !isArray(n) && !isNaN(parseFloat(n)) && isFinite(n); | |
} | |
function isArray(arr) { | |
if (Object.prototype.toString.call([]) === '[object Array]') { | |
return Array.isArray(arr) || (typeof arr === 'object' && Object.prototype.toString.call(arr) === '[object Array]'); | |
} | |
else { | |
throw new Exception('toString message changed for Object Array'); | |
} | |
} | |
function insertCharAtPosition(str, char, caretPosition) { | |
return str.slice(0, caretPosition)+char+str.slice(caretPosition); | |
} | |
function Exception(msg) { | |
this.message = msg || ''; | |
this.name = "Exception"; | |
} | |
Exception.prototype = new Error(); | |
Exception.prototype.constructor = Exception; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="container"> | |
<div id="app"> | |
<div class="problem"> | |
<p>The problem is : whenever you input a number in the first 'a' input, the v-model gets updated in both 'a' inputs. However, if you click in the second 'a' input, it then shows its initial value, not the v-model value.</p> | |
<p>The same problem arise when two inputs do not share a `v-model`, but update each other via vue.js.</p> | |
<p class="solved">Ok, the culprit has been found ; the current <strong>autoNumeric 2.0 Beta</strong> version keeps a `rawValue` and use that on focus if the input has changed. This has been added in 2.0 to circumvent a weird Asp.net behavior. So, until there is a way to be able to use autoNumeric 2.0 like before, <strong class="reallyStrong">you'll have to use 1.9.46 for now</strong>.</p> | |
</div> | |
<div class="example example1"> | |
<p>Example 1 : Two inputs <strong>sharing the same `v-model`</strong> :</p> | |
<div class="frow frow1"> | |
<div>a:<input type="text" v-cdz-numeric v-model="a"></div> | |
<div>a:<input type="text" v-cdz-numeric v-model="a"></div> | |
</div> | |
<div class="frow frow2"> | |
<div>b:<input class="unimportant" type="text" v-cdz-numeric="{ aPad: false }" v-model="b"></div> | |
<div>c:<input class="unimportant" type="text" v-cdz-numeric="{ aSep: '.', aDec: ',', altDec: '.', aSign: '', pSign: 's', mRound: 'U' }" v-model="c"></div> | |
</div> | |
<p class="vModelValues">v-model input values : [<span class="shared">{{a}}</span>] [<span class="shared">{{a}}</span>] [<span class="c1">{{b}}</span>] [<span>{{c}}</span>]</p> | |
</div> | |
<div class="example example2"> | |
<div> | |
<p>Example 2 : Two inputs <strong>using different `v-model`</strong>, but having their values updated via vue.js, and not via <i>'normal'</i> user inputs :</p> | |
<div class="frow frow3"> | |
<div>y:<input type="text" v-model="years" ref="years" v-cdz-numeric="{aSep: '', aDec: '.', altDec: ',', aSign: '', pSign: 's', mRound: 'U', vMin: '-9999999999.99', vMax: '9999999999.99', aPad: false}"></div> | |
<div>m:<input type="text" v-model="months" ref="months" v-cdz-numeric="{aSep: '', aDec: '.', altDec: ',', aSign: '', pSign: 's', mRound: 'U', vMin: '-9999999999.99', vMax: '9999999999.99', aPad: false}"></div> | |
</div> | |
</div> | |
<p class="vModelValues">v-model input values : [<span class="shared2">{{years}}</span>] [<span class="shared2">{{months}}</span>]</p> | |
</div> | |
<p><i class="notes">Watch out, do not use `v-model.number` here, otherwise this messes up with autoNumeric inserting 'Comma' inside the input value.</i></p> | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#app { | |
display : flex; | |
flex-direction : column; | |
align-items : center; | |
} | |
.solved { | |
color : lime; | |
} | |
.reallyStrong { | |
text-decoration: underline; | |
} | |
.problem { | |
width : 55%; | |
margin-top : 1rem; | |
margin-bottom : 2rem; | |
} | |
.hello { | |
color : #eee; | |
height : 15vh; | |
line-height : 15vh; | |
user-select : none; | |
cursor : pointer; | |
transition : color 0.3s ease; | |
&:hover { | |
color: lime; | |
} | |
} | |
#normalInput { | |
margin-top : 4rem; | |
} | |
.example p:first-child { | |
color : red; | |
background-color : #222; | |
// margin-top : 2rem; | |
margin-bottom : 0.5rem; | |
padding : 0.5rem; | |
} | |
i.notes { | |
font-size : 0.8rem; | |
} | |
.frow { | |
display : flex; | |
justify-content : space-between; | |
font-size : 2rem; | |
& > div:first-child { | |
margin-right: 2rem; | |
} | |
} | |
.frow1 { | |
margin-bottom: 1rem; | |
input { | |
border : 1px solid yellow; | |
} | |
} | |
.frow3 input { | |
border : 1px solid cyan; | |
} | |
input[type="text"] { | |
text-align:right; | |
min-width: 150px; | |
min-height : 40px; | |
border-radius : 2px; | |
border : 1px solid #eee; | |
background-color : #ccc; | |
font-size : 2rem; | |
color : #333; | |
padding : 1rem; | |
&.unimportant { | |
background-color: #949494; | |
color : #555; | |
} | |
} | |
.vModelValues { | |
font-size: 1.4rem; | |
padding-left : 2rem; | |
margin-bottom : 3rem; | |
span { | |
color : magenta; | |
&.shared { | |
color : yellow; | |
} | |
&.shared2 { | |
color : cyan; | |
} | |
&.c1 { | |
color : lime; | |
} | |
} | |
} | |
//-------------------- Unimportant css rules -------------------- | |
$transitionDuration : 0.3s; | |
* { | |
margin : 0; | |
padding : 0; | |
position : relative; | |
box-sizing : border-box; | |
} | |
button:-moz-focusring, | |
input:-moz-focusring { | |
outline: 0; | |
} | |
.container { | |
height : 100vh; | |
display : flex; | |
flex-direction : column; | |
justify-content : center; | |
align-items : center; | |
} | |
.filler { | |
background-color : #12232F; | |
border : 1px solid #3F4245; | |
opacity : 0.80; | |
height : 20vh; | |
width : 100vw; | |
} | |
body { | |
background : #333; //#022238 | |
color : white; | |
font-family : "Open sans", sans-serif; | |
font-weight : 100; | |
font-size : 1rem; | |
} | |
a.button { | |
// color : white; | |
color : yellow; | |
text-decoration : none; | |
transition : color $transitionDuration ease; | |
&:focus { | |
outline: none; | |
} | |
&:hover { | |
// color : deeppink; | |
color : lime; | |
} | |
} | |
.button2 { | |
background-color : deeppink; | |
color : white; | |
border : 1px solid red; | |
border-radius : 2px; | |
height : 30px; | |
cursor : pointer; | |
outline : none; | |
&:focus, &:active { | |
outline : none; | |
} | |
} | |
h1 { | |
margin : 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment