/** * fullscreenForm.js v1.0.0 * http://www.codrops.com * * Licensed under the MIT license. * http://www.opensource.org/licenses/mit-license.php * * Copyright 2014, Codrops * http://www.codrops.com */ ; (function(window) { 'use strict'; var support = { animations: Modernizr.cssanimations }, animEndEventNames = { 'WebkitAnimation': 'webkitAnimationEnd', 'OAnimation': 'oAnimationEnd', 'msAnimation': 'MSAnimationEnd', 'animation': 'animationend' }, // animation end event name animEndEventName = animEndEventNames[Modernizr.prefixed('animation')]; /** * extend obj function */ function extend(a, b) { for (var key in b) { if (b.hasOwnProperty(key)) { a[key] = b[key]; } } return a; } /** * createElement function * creates an element with tag = tag, className = opt.cName, innerHTML = opt.inner and appends it to opt.appendTo */ function createElement(tag, opt) { var el = document.createElement(tag) if (opt) { if (opt.cName) { el.className = opt.cName; } if (opt.inner) { el.innerHTML = opt.inner; } if (opt.appendTo) { opt.appendTo.appendChild(el); } } return el; } // finds no of occurances of substring function occurrences(string, subString, allowOverlapping) { string += ""; subString += ""; if (subString.length <= 0) return (string.length + 1); var n = 0, pos = 0, step = allowOverlapping ? 1 : subString.length; while (true) { pos = string.indexOf(subString, pos); if (pos >= 0) { ++n; pos += step; } else break; } return n; } function isTransfer(text) { var wordlist = ['transfer', 'send', 'give']; // keep list's content lowercase var textList = text.split(' '); for (var i = 0; i < wordlist.length; i++) { if (textList.includes(wordlist[i])) { return true; } } return false; } function isIncorp(text) { var wordlist = ['incorporate', 'create', 'start', 'begin']; // keep list's content lowercase var textList = text.split(' '); for (var i = 0; i < wordlist.length; i++) { if (textList.includes(wordlist[i])) { return true; } } return false; } function extractAmount(text) { var count = 0; var returnval; var splitText = text.split(/\W+/); for (var i = 0; i < splitText.length; i++) { if (parseFloat(splitText[i])) { count += 1; returnval = parseFloat(splitText[i]); } if (count > 1) { return 0; } } return returnval; } function extractAddress(text) { var textList = text.split(' ') for (var i = 0; i < textList.length; i++) { if (textList[i] == '') { continue; } if (textList[i].endsWith('$') && textList[i].length != 1) { return textList[i]; } } return 0; } function extractInitTokens(text) { var base_units = { 'thousand': 10 ** 3, 'million': 10 ** 6, 'billion': 10 ** 9, 'trillion': 10 ** 12 }; var textList = text.split(' '); var counter = 0; var value; for (var i = 0; i < textList.length; i++) { if (!isNaN(textList[i])) { if (base_units.hasOwnProperty(textList[i + 1])) { value = textList[i] * base_units[textList[i + 1]]; counter += 1; } else{ value = parseFloat(textList[i]); counter = counter + 1; } } else { for (var j = 0; j < Object.keys(base_units).length; j++) { var result = textList[i].split(Object.keys(base_units)[j]); if (result.length == 2 && result[1] == '' && result[0] != '') { if (!isNaN(result[0])) { value = parseFloat(result[0]) * base_units[Object.keys(base_units)[j]]; counter = counter + 1; } } } } } if (counter == 1) { return value; } else { return 0; } } function parse_flodata(flodata) { debugger; if (flodata.slice(0, 5) == 'text') { flodata = flodata.split('text:')[1]; } var nospacestring = flodata.replace(/ +/g, ' '); var cleanstring = nospacestring.toLowerCase(); var parsed_data; var atList = []; var hashList = []; var cleanstringList = cleanstring.split(' ') for (var i = 0; i < cleanstringList.length; i++) { if (cleanstringList[i].endsWith('@') && cleanstringList[i].length != 1) { atList.push(cleanstringList[i]); } if (cleanstringList[i].endsWith('#') && cleanstringList[i].length != 1) { hashList.push(cleanstringList[i]); } } if (atList.length == 0 && hashList.length == 0 || atList.length > 1 || hashList.length > 1) { parsed_data = { 'type': 'noise', 'flodata': nospacestring }; } else if (hashList.length == 1 && atList.length == 0) { // Passing the above check means token creation or transfer var incorporation = isIncorp(cleanstring); var transfer = isTransfer(cleanstring); //todo Rule 27 - if (neither token incorporation and token transfer) OR both token incorporation and token transfer, reject if ((!incorporation && !transfer) || (incorporation && transfer)) { parsed_data = { 'type': 'noise', 'flodata': nospacestring }; } else if (incorporation && !transfer) { var initTokens = extractInitTokens(cleanstring); if (initTokens != 0) { parsed_data = { 'type': 'tokenIncorporation', 'flodata': nospacestring, 'tokenIdentification': hashList[0].slice(0, hashList[0].length - 1), 'tokenAmount': initTokens }; } else { parsed_data = { 'type': 'noise', 'flodata': nospacestring }; } } // todo Rule 30 - if not token creation and is token transfer then then process it for token transfer rules // todo Rule 31 - Extract number of tokens to be sent and the address to which to be sent, both data is mandatory else if (!incorporation && transfer) { var amount = extractAmount(cleanstring, hashList[0].slice(0, hashList[0].length - 1)); if (amount != 0) { parsed_data = { 'type': 'transfer', 'transferType': 'token', 'flodata': nospacestring, 'tokenIdentification': hashList[0].slice(0, hashList[0].length - 1), 'tokenAmount': amount }; } else { parsed_data = { 'type': 'noise', 'flodata': nospacestring }; } } } else { parsed_data = { 'type': 'noise', 'flodata': nospacestring }; } return parsed_data; } /** * FForm function */ function FForm(el, options) { this.el = el; this.options = extend({}, this.options); extend(this.options, options); this._init(); } /** * FForm options */ FForm.prototype.options = { // show progress bar ctrlProgress: true, // show navigation dots ctrlNavDots: true, // show [current field]/[total fields] status ctrlNavPosition: true, // reached the review and submit step onReview: function() { return false; } }; /** * init function * initialize and cache some vars */ FForm.prototype._init = function() { // the form element this.formEl = this.el.querySelector('form'); // list of fields this.fieldsList = this.formEl.querySelector('ol.fs-fields'); // current field position this.current = 0; // all fields this.fields = [].slice.call(this.fieldsList.children); // total fields this.fieldsCount = this.fields.length; // show first field classie.add(this.fields[this.current], 'fs-current'); // create/add controls this._addControls(); // create/add messages this._addErrorMsg(); // init events this._initEvents(); }; /** * addControls function * create and insert the structure for the controls */ FForm.prototype._addControls = function() { // main controls wrapper this.ctrls = createElement('div', { cName: 'fs-controls', appendTo: this.el }); // continue button (jump to next field) this.ctrlContinue = createElement('button', { cName: 'fs-continue', inner: 'Continue', appendTo: this.ctrls }); this._showCtrl(this.ctrlContinue); // navigation dots if (this.options.ctrlNavDots) { this.ctrlNav = createElement('nav', { cName: 'fs-nav-dots', appendTo: this.ctrls }); var dots = ''; for (var i = 0; i < this.fieldsCount; ++i) { dots += i === this.current ? '' : ''; } this.ctrlNav.innerHTML = dots; this._showCtrl(this.ctrlNav); this.ctrlNavDots = [].slice.call(this.ctrlNav.children); } // field number status if (this.options.ctrlNavPosition) { this.ctrlFldStatus = createElement('span', { cName: 'fs-numbers', appendTo: this.ctrls }); // current field placeholder this.ctrlFldStatusCurr = createElement('span', { cName: 'fs-number-current', inner: Number(this.current + 1) }); this.ctrlFldStatus.appendChild(this.ctrlFldStatusCurr); // total fields placeholder this.ctrlFldStatusTotal = createElement('span', { cName: 'fs-number-total', inner: this.fieldsCount }); this.ctrlFldStatus.appendChild(this.ctrlFldStatusTotal); //this._showCtrl(this.ctrlFldStatus); } // progress bar if (this.options.ctrlProgress) { this.ctrlProgress = createElement('div', { cName: 'fs-progress', appendTo: this.ctrls }); this._showCtrl(this.ctrlProgress); } } /** * addErrorMsg function * create and insert the structure for the error message */ FForm.prototype._addErrorMsg = function() { // error message this.msgError = createElement('span', { cName: 'fs-message-error', appendTo: this.el }); } /** * init events */ FForm.prototype._initEvents = function() { var self = this; // show next field this.ctrlContinue.addEventListener('click', function() { var flodata = document.getElementById('q1'); var result = parse_flodata(flodata.value); console.log(result); self._nextField(undefined, result); }); // navigation dots if (this.options.ctrlNavDots) { this.ctrlNavDots.forEach(function(dot, pos) { dot.addEventListener('click', function() { self._showField(pos); }); }); } // jump to next field without clicking the continue button (for fields/list items with the attribute "data-input-trigger") this.fields.forEach(function(fld) { if (fld.hasAttribute('data-input-trigger')) { var input = fld.querySelector('input[type="radio"]') || /*fld.querySelector( '.cs-select' ) ||*/ fld.querySelector('select'); // assuming only radio and select elements (TODO: exclude multiple selects) if (!input) return; switch (input.tagName.toLowerCase()) { case 'select': input.addEventListener('change', function() { self._nextField(); }); break; case 'input': [].slice.call(fld.querySelectorAll('input[type="radio"]')).forEach(function(inp) { inp.addEventListener('change', function(ev) { self._nextField(); }); }); break; /* // for our custom select we would do something like: case 'div' : [].slice.call( fld.querySelectorAll( 'ul > li' ) ).forEach( function( inp ) { inp.addEventListener( 'click', function(ev) { self._nextField(); } ); } ); break; */ } } }); // keyboard navigation events - jump to next field when pressing enter document.addEventListener('keydown', function(ev) { if (!self.isLastStep && ev.target.tagName.toLowerCase() !== 'textarea') { var keyCode = ev.keyCode || ev.which; if (keyCode === 13) { ev.preventDefault(); var flodata = document.getElementById('q1'); var result = parse_flodata(flodata.value); console.log(result); self._nextField(undefined, result); } } }); }; /** * nextField function * jumps to the next field */ FForm.prototype._nextField = function(backto, result) { if (this.isLastStep || !this._validade() || this.isAnimating) { return false; } this.isAnimating = true; // check if on last step this.isLastStep = this.current === this.fieldsCount - 1 && backto === undefined ? true : false; // clear any previous error messages this._clearError(); // current field var currentFld = this.fields[this.current]; // save the navigation direction this.navdir = backto !== undefined ? backto < this.current ? 'prev' : 'next' : 'next'; // update current field this.current = backto !== undefined ? backto : this.current + 1; if (backto === undefined) { // update progress bar (unless we navigate backwards) this._progress(); // save farthest position so far this.farthest = this.current; } // add class "fs-display-next" or "fs-display-prev" to the list of fields classie.add(this.fieldsList, 'fs-display-' + this.navdir); // remove class "fs-current" from current field and add it to the next one // also add class "fs-show" to the next field and the class "fs-hide" to the current one classie.remove(currentFld, 'fs-current'); classie.add(currentFld, 'fs-hide'); if (!this.isLastStep) { // update nav this._updateNav(); // change the current field number/status this._updateFieldNumber(); var nextField = this.fields[this.current]; classie.add(nextField, 'fs-current'); classie.add(nextField, 'fs-show'); } // after animation ends remove added classes from fields var self = this, onEndAnimationFn = function(ev) { if (support.animations) { this.removeEventListener(animEndEventName, onEndAnimationFn); } classie.remove(self.fieldsList, 'fs-display-' + self.navdir); classie.remove(currentFld, 'fs-hide'); if (self.isLastStep) { // show the complete form and hide the controls self._hideCtrl(self.ctrlNav); self._hideCtrl(self.ctrlProgress); self._hideCtrl(self.ctrlContinue); self._hideCtrl(self.ctrlFldStatus); // replace class fs-form-full with fs-form-overview classie.remove(self.formEl, 'fs-form-full'); classie.add(self.formEl, 'fs-form-overview'); classie.add(self.formEl, 'fs-show'); classie.add(self.formEl, 'hideElement'); // Result display page var div = document.createElement("div"); div.setAttribute("id", "resultPage"); div.setAttribute("class", "fs-form fs-form-overview fs-show"); var judgement = document.createElement("div"); judgement.setAttribute("class", "show-result"); var ol = document.createElement('ol'); ol.setAttribute("class", "fs-fields"); if (result['type'] == 'transfer') { var fieldnames = [ { 'Type': 'Transfer' }, { 'Token name': result['tokenIdentification'] }, { 'Transfer amount': result['tokenAmount'] }, {'Original FLO data': result['flodata'] }]; judgement.innerHTML = 'Passed!'; } else if (result['type'] == 'tokenIncorporation') { var fieldnames = [{ 'Type': 'Incorporation' }, { 'Token name': result['tokenIdentification'] }, { 'Incorporation amount': result['tokenAmount'] }, {'Original FLO data': result['flodata'] }]; judgement.innerHTML = 'Passed!'; } else { var fieldnames = [{ 'Type': 'Noise' }, {'Original FLO data': result['flodata'] }]; judgement.innerHTML = 'Parsing failed!'; } for (var i = 0; i < fieldnames.length; i++) { var item = document.createElement('li'); var label = document.createElement('label'); label.setAttribute('class', 'fs-field-label fs-anim-upper'); label.setAttribute('for', 'q' + i.toString()); label.innerHTML = Object.keys(fieldnames[i])[0] var input = document.createElement('input'); input.setAttribute('class', 'fs-anim-lower'); input.setAttribute("id", 'q' + i.toString()); input.setAttribute("name", 'q' + i.toString()); input.setAttribute("readonly", "readonly"); input.setAttribute("type", "text"); input.setAttribute("value", fieldnames[i][label.innerHTML]); item.appendChild(label); item.appendChild(input); ol.appendChild(item); } var returnButton = document.createElement('button'); returnButton.setAttribute('class', 'fs-submit'); returnButton.setAttribute('type', 'submit'); returnButton.innerHTML = 'Go Back'; returnButton.onclick = function() { location.href = "index.html"; }; div.appendChild(judgement) div.appendChild(ol); div.appendChild(returnButton); var formElement = document.getElementById('myform'); formElement.insertAdjacentElement('afterend', div); // callback self.options.onReview(); } else { classie.remove(nextField, 'fs-show'); if (self.options.ctrlNavPosition) { self.ctrlFldStatusCurr.innerHTML = self.ctrlFldStatusNew.innerHTML; self.ctrlFldStatus.removeChild(self.ctrlFldStatusNew); classie.remove(self.ctrlFldStatus, 'fs-show-' + self.navdir); } } self.isAnimating = false; }; if (support.animations) { if (this.navdir === 'next') { if (this.isLastStep) { currentFld.querySelector('.fs-anim-upper').addEventListener(animEndEventName, onEndAnimationFn); } else { nextField.querySelector('.fs-anim-lower').addEventListener(animEndEventName, onEndAnimationFn); } } else { nextField.querySelector('.fs-anim-upper').addEventListener(animEndEventName, onEndAnimationFn); } } else { onEndAnimationFn(); } } /** * showField function * jumps to the field at position pos */ FForm.prototype._showField = function(pos) { if (pos === this.current || pos < 0 || pos > this.fieldsCount - 1) { return false; } this._nextField(pos); } /** * updateFieldNumber function * changes the current field number */ FForm.prototype._updateFieldNumber = function() { if (this.options.ctrlNavPosition) { // first, create next field number placeholder this.ctrlFldStatusNew = document.createElement('span'); this.ctrlFldStatusNew.className = 'fs-number-new'; this.ctrlFldStatusNew.innerHTML = Number(this.current + 1); // insert it in the DOM this.ctrlFldStatus.appendChild(this.ctrlFldStatusNew); // add class "fs-show-next" or "fs-show-prev" depending on the navigation direction var self = this; setTimeout(function() { classie.add(self.ctrlFldStatus, self.navdir === 'next' ? 'fs-show-next' : 'fs-show-prev'); }, 25); } } /** * progress function * updates the progress bar by setting its width */ FForm.prototype._progress = function() { if (this.options.ctrlProgress) { this.ctrlProgress.style.width = this.current * (100 / this.fieldsCount) + '%'; } } /** * updateNav function * updates the navigation dots */ FForm.prototype._updateNav = function() { if (this.options.ctrlNavDots) { classie.remove(this.ctrlNav.querySelector('button.fs-dot-current'), 'fs-dot-current'); classie.add(this.ctrlNavDots[this.current], 'fs-dot-current'); this.ctrlNavDots[this.current].disabled = false; } } /** * showCtrl function * shows a control */ FForm.prototype._showCtrl = function(ctrl) { classie.add(ctrl, 'fs-show'); } /** * hideCtrl function * hides a control */ FForm.prototype._hideCtrl = function(ctrl) { classie.remove(ctrl, 'fs-show'); } // TODO: this is a very basic validation function. Only checks for required fields.. FForm.prototype._validade = function() { var fld = this.fields[this.current], input = fld.querySelector('input[required]') || fld.querySelector('textarea[required]') || fld.querySelector('select[required]'), error; if (!input) return true; switch (input.tagName.toLowerCase()) { case 'input': if (input.type === 'radio' || input.type === 'checkbox') { var checked = 0; [].slice.call(fld.querySelectorAll('input[type="' + input.type + '"]')).forEach(function(inp) { if (inp.checked) { ++checked; } }); if (!checked) { error = 'NOVAL'; } } else if (input.value === '') { error = 'NOVAL'; } break; case 'select': // assuming here '' or '-1' only if (input.value === '' || input.value === '-1') { error = 'NOVAL'; } break; case 'textarea': if (input.value === '') { error = 'NOVAL'; } break; } if (error != undefined) { this._showError(error); return false; } return true; } // TODO FForm.prototype._showError = function(err) { var message = ''; switch (err) { case 'NOVAL': message = 'Please fill the field before continuing'; break; case 'INVALIDEMAIL': message = 'Please fill a valid email address'; break; // ... }; this.msgError.innerHTML = message; this._showCtrl(this.msgError); } // clears/hides the current error message FForm.prototype._clearError = function() { this._hideCtrl(this.msgError); } // add to global namespace window.FForm = FForm; })(window);