How to validate multiple user inputs within just one popup using Vue-SweetAlert2 - vue.js

As a coding training, right now I'm making a web page where you can click a "Create" button, which triggers a popup, where you are supposed to fill in 6 data inputs, whose input style varies like text, select etc. (See the code and the attached image below)
<template>
<v-btn class="create-button" color="yellow" #click="alertDisplay">Create</v-btn>
<br/>
<p>Test result of createCustomer: {{ createdCustomer }}</p>
</div>
</template>
<script>
export default {
data() {
return {
createdCustomer: null
}
},
methods: {
alertDisplay() {
const {value: formValues} = await this.$swal.fire({
title: 'Create private customer',
html: '<input id="swal-input1" class="swal2-input" placeholder="Customer Number">' +
'<select id="swal-input2" class="swal2-input"> <option value="fi_FI">fi_FI</option> <option value="sv_SE">sv_SE</option> </select>'
+
'<input id="swal-input3" class="swal2-input" placeholder="regNo">' +
'<input id="swal-input4" class="swal2-input" placeholder="Address">' +
'<input id="swal-input5" class="swal2-input" placeholder="First Name">' +
'<input id="swal-input6" class="swal2-input" placeholder="Last Name">'
,
focusConfirm: false,
preConfirm: () => {
return [
document.getElementById('swal-input1').value,
document.getElementById('swal-input2').value,
document.getElementById('swal-input3').value,
document.getElementById('swal-input4').value,
document.getElementById('swal-input5').value,
document.getElementById('swal-input6').value
]
}
})
if (formValues) {
this.createdCustomer = this.$swal.fire(JSON.stringify(formValues));
console.log(this.createdCustomer);
}
}
}
}
</script>
Technically, it's working. The popup shows up when the "create" button is clicked, and you can fill in all the 6 blanks and click the "OK" button as well. But I want to add some functionalities that check if the user inputs are valid, I mean things like
address should be within 50 characters
firstName should be within 20 characters
customerNumber should include both alphabets and numbers
and so on.
If it were C or Java, I could probably do something like
if(length <= 50){
// proceed
} else {
// warn the user that the string is too long
}
, but when it comes to validating multiple inputs within a single popup using Vue-SweetAlert2, I'm not sure how to do it, and I haven't been able to find any page that explains detailed enough.
If it were just a single input, maybe you could use inputValidor like this
const {value: ipAddress} = await Swal.fire({
title: 'Enter an IP address',
input: 'text',
inputValue: inputValue,
showCancelButton: true,
inputValidator: (value) => {
if (!value) {
return 'You need to write something!'
}
}
})
if (ipAddress) {
Swal.fire(`Your IP address is ${ipAddress}`)
}
, but this example only involves "one input". Plus, what this checks is just "whether an IP address has been given or not" (, which means whether there is a value or not, and it doesn't really check if the length of the IP address is correct and / or whether the input string consists of numbers / alphabets).
On the other hand, what I'm trying to do is to "restrict multiple input values (such as the length of the string etc)" "within a single popup". Any idea how I am supposed to do this?

Unfortunately the HTML tags to restrict inputs (e.g. required, pattern, etc.) do not work (see this issues),
so I find two work around.
Using preConfirm as in the linked issues
You could use preConfirm and if/else statement with Regex to check your requirement, if they are not satisfied you could use Swal.showValidationMessage(error).
const{value:formValue} = await Swal.fire({
title: "Some multiple input",
html:
<input id="swal-input1" class="swal-input1" placeholder="name"> +
<input id="swal-input2" class="swal-input2" placeholder="phone">,
preConfirm: () => {
if($("#swal-input1").val() !== "Joe"){
Swal.showValidationMessage("your name must be Joe");
} else if (!('[0-9]+'.test($("#swal-input2").val())){
Swal.showValidationMessage("your phone must contain some numbers");
}
}
});
Using Bootstrap
In this way Bootstrap does the check at the validation, you need to include class="form-control" in your input class and change a little your html code.
If some conditions fails, the browser shows a validation message for each fields, in the order they are in the html code.
const {value: formValue} = await Swal.fire({
title: 'Some multiple inputs',
html:
'<form id="multiple-inputs">' +
'<div class="form-group">' +
'<input type="text" class="form-control swal-input1" id="swal-input1" min=2 max=4 required>' +
'</div>' +
'<div class="form-group">' +
'<input type="text" class="form-control swal-input2" id="swal-input2" placeholder="Name" pattern="[A-Za-z]" required>' +
'</div>' +
'</form>',
});
I have tried both the solution, actually only with Bootstrap3 but it should work also with the latest release.

Related

Concat value into API url in Vue

I am trying something very simple in Vue and can't get it to work.. I want my API url to update with 2 new values (valueFrom and valueTwo). I am using fetch for this.
When I console log the values, they work (2021-06-17 etc). But as soon as I put them in the url, it just comes up empty.
Here are my input fields:
<label for="dateFrom" class="mr-sm-2">From</label>
<b-form-datepicker id="dateFrom" v-model="valueFrom" class="mb-2 mr-sm-4 mb-sm-0 w-25" :min="min" :max="max"></b-form-datepicker>
<label for="dateTo" class="mr-sm-2">To</label>
<b-form-datepicker id="dateTo" v-model="valueTo" class="mb-2 mr-sm-4 mb-sm-0 w-25" :min="min" :max="max"></b-form-datepicker>
Here is the button that calls the fetch:
<b-button variant="primary" #click="$fetch()">Search</b-button>
Here is my Vue code:
data() {
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const minDate = new Date(today);
const maxDate = new Date(today);
maxDate.setMonth(maxDate.getMonth() + 6);
return {
items: {},
valueFrom: '',
valueTo: '',
min: minDate,
max: maxDate
}
},
async fetch() {
const api = 'https://apiurl.com/ajax/locationSearchJSON/?location=Edinburgh&arrival=${this.valueFrom}&arrivalTime=12%3A00&departure={$this.valueTo}&departureTime=12%3A00
this.items = await fetch(api).then(res => res.json())
}
I also tried concating them in the javascript way (' + this.valueFrom + ') but that just rendered an empty string as well...
You can try using the backticks/backquotes (``) in place of the apostrophes. So your url will end up as below:
`https://apiurl.com/ajax/locationSearchJSON/?location=Edinburgh&arrival=${this.valueFrom}&arrivalTime=12%3A00&departure=${this.valueTo}&departureTime=12%3A00`
Also check the second variable this.valueTo has been added to the url correctly. In your case above this is how you've added it:
{$this.valueTo}
It should be:
${this.valueTo}

How do I get the value from sibling of selected option and pass it as value in inputfield autofill (VueJS)

Flow of my app:
user types a city
user select a country code in select
then submit
But the problem is that when someone types 'ams' instead of 'amsterdam' it will also output a list of responding country codes. If someone select one and then click on the submit button. It will give an error that the this.city is undefined. The reason for this is because the url that is fetched consists variables of this.city and this.country_code. Something looks like www.weatherbit.com/${this.city}=country${this.country_code} and that is the reason why it gives an error in this case. So in order to avoid that I want to get the autofill option if the this.city is not a valid city name. When someone clicks on country code 'NL' and the city name is undefined (not matching the entire city name) it will autofill the input field with that. In order to do so it needs to get the 'country.city_name' and pass it to v-model="city". How can I do that.
<select v-model="countryCode" #change="handleChange">
<option v-for="(country, index) in checkCountryName" :key="index" :value="country.country_code" :selected="index === 0">
{{ country.city_name}} // only this value should be output in input /// and should not be in option element
{{ country.country_code }} // only this should be shown in option if this is undefined
</option>
</select>
<input v-model="city" placeholder="Please enter your location...">
data () {
return {
city: '',
cityList: [],
countryCode: null,
errors: []
}
},
mounted () {
axios.get('https://gist.githubusercontent.com/chiholiu/9243c14cd7f310c0866947414496ad99/raw/8f97404080b3812477004793e2318552fc876aa2/cities.json')
.then(response => {
this.cityList = response.data
})
},
computed: {
checkCountryName () {
if(this.city.length < 1) return;
return this.cityList.filter((cityName) => {
return cityName.city_name.toLowerCase().match(this.city.toLowerCase())
})
}
},
There are few options to achieve this scenario.
Option 1: Enter full city name
Instead of match, find the city the user typed is fully matching the city_name. This will give you the full city name when you call the api.
Option 2: Change the input value
Once the user typed the half word like ams and select a country, at handleChange event in select, you can set the city_name to city like
methods: {
handleChange(e) {
this.city = e.target.value.city_name
}
}

Angular5 data prepopulation one way binding not happening

I am using a parent component travelerInput that creates travelerListForm using model in my component and iterate it extracting travelerForm which is then passed to the nested child components using #Input:
for (let i = 1; i <= numberOfTravelers; i++) {
const tId = `${ptc}_0${i}`;
const Id = `${TravelerInput.pIndex++}`;
const traveler = new Traveler({passengerTypeCode: ptc, id: Id, tid: tId, names: [new Identity({
firstName: "",
middleName: "",
lastName: "",
title: "",
nameType: "native",
isDisplayed: false
})],
dateOfBirth: undefined ,
gender: "unknown",
accompanyingTravelerId: "",
accompanyingTravelerTid: ""
});
travelerList.push(traveler);
}
HTML
<div class="alpi-section col-xs-12 col-sm-12 col-md-12 col-lg-12"
*ngFor="let travelerForm of travelerListForm.controls; let tIndex = index;">
<o3r-simple-traveler-input
[config]="config.simpleTravelerInput"
[travelerForm]="travelerForm"
[index]="tIndex">
</o3r-simple-traveler-input>
Now we have a drop down in parent component with a list of travelers. The selected passenger in the drop down will have its information prepopulated in the form fields which are nested child components. I am using travelerForm which is iterated over travelerListForm in child components as #Input. On change of drop down I am binding the value of the passenger information to the corresponding index of travelerListForm which is also getting updated but on UI there is no update.
pickSelectedADTPassenger(adult:any, index: number){
this.selectedADTId= this.ADTTravelerId[adult];
this.travelerListForm.controls[index].value.names[0].firstName = this.selectedADTId.IDENTITY_INFORMATION.FIRST_NAME; //ASSISGNMENT
}
Have also tried using ngModel in the child component input field where I want the value to be prepopulated but it did not work:
<input type="text"
[(ngModel)]="travelerForm.controls.firstName.value"
class="form-control"
placeholder="FIRST NAME"
maxlength="52"
formControlName="firstName">
</div>
Please suggest.

Product autocomplete input on module (Prestashop)

I'm developing a prestashop module that has to make lists of existing products.
For the configuration panel of the module, using renderForm() and getContent(), I'm trying to replicate the "accesories" capability, where you start writing some info of a product on an input, and it shows the products that are a match. When selecting that product, it gets added on a list. Like this:
This a screenshot of Catalog / Products / Associations tab.
I'm trying with PS 1.6.0.14 and PS1.6.1.0RC3. How would I replicate this functionality to get lists of products on a module configuration panel?
I tried looking here Prestashop AdminProductsController.php but I don't really understand where half of that info is coming from.
There is an autocomplete plugin in prestashop you got to use that for this. Its in js->jquery->plugins you got to add this plugin into your module to make it work.
I think that to achieve that functionality, the renderForm() function won't be enough since you have to bind some javascript and some custom html.
The process of writing a fully functional module is a bit long but by taking the accessories functionality as a starting point it wont be so hard and you will always have a reference on "how-to-do-it".
I would go with this:
1) first create your
getContent()
function to be able to show the custom template and the product associated by your module so we will have something along:
public function getContent(){
//post process part to save the associations
if(Tools::isSubmit('saveMyAssociations'){
... //we will see it later
}
$my_associations = MyModule::getAssociationsLight($this->context->language->id,Tools::getValue('id_product')); //function that will retrieve the array of all the product associated on my module table.
$this->context->smarty->assign(array(
'my_associations' => $my_associations,
'product_id' => (int)Tools::getValue('id_product')
));
return $this->display(__FILE__, 'views/templates/admin/admintemplate.tpl'); //custome template to create the autocomplete
}
//our little function to get the already saved list, for each product we will retrieve id, name and reference with a join on the product/product_lang tables.
public static function getAssociationsLight($id_lang, $id_product, Context $context = null)
{
if (!$context)
$context = Context::getContext();
$sql = 'SELECT p.`id_product`, p.`reference`, pl.`name`
FROM `'._DB_PREFIX_.'my_associations`
LEFT JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product`= `id_product_2`)
'.Shop::addSqlAssociation('product', 'p').'
LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
p.`id_product` = pl.`id_product`
AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
)
WHERE `id_product_1` = '.(int)$id_product;
return Db::getInstance()->executeS($sql);
}
2) create a template that will be able to show the automplete and the list.
Here we will loop trough the saved associations to create our autocomplete list, and we will do it with some hidden field to keep track of the ids/name and also a visible list were we will have a delete button for each row.
<input type="hidden" name="inputMyAssociations" id="inputMyAssociations" value="{foreach from=$my_associations item=accessory}{$accessory.id_product}-{/foreach}" />
<input type="hidden" name="nameMyAssociations" id="nameMyAssociations" value="{foreach from=$my_associations item=accessory}{$accessory.name|escape:'html':'UTF-8'}¤{/foreach}" />
<div id="ajax_choose_product_association">
<div class="input-group">
<input type="text" id="product_autocomplete_input_association" name="product_autocomplete_input_association" />
<span class="input-group-addon"><i class="icon-search"></i></span>
</div>
</div>
<div id="divMyAssociations">
{foreach from=$my_associations item=accessory}
<div class="form-control-static">
<button type="button" class="btn btn-default delAssociation" name="{$accessory.id_product}">
<i class="icon-remove text-danger"></i>
</button>
{$accessory.name|escape:'html':'UTF-8'}{if !empty($accessory.reference)}{$accessory.reference}{/if}
</div>
{/foreach}
</div>
<input type="submit" name="submitMyAssociations" id="submitMyAssociations" value="Send"/>
<input type="hidden" name="productId" id="productId" value="{$product_id|escape:'html'}"/>
3) Now we can add the javascript to bind an autocomplete on the main input and perform all the logic for each action
$(document).ready(function(){
//our function wrapper.
var initMyAssociationsAutocomplete = function (){
//initialize the autocomplete that will point to the default ajax_products_list page (it returns the products by id+name)
$('#product_autocomplete_input_association')
.autocomplete('ajax_products_list.php', {
minChars: 1,
autoFill: true,
max:20,
matchContains: true,
mustMatch:true,
scroll:false,
cacheLength:0,
formatItem: function(item) {
return item[1]+' - '+item[0];
}
}).result(addAssociation);
//as an option we will add a function to exclude a product if it's already in the list
$('#product_autocomplete_input_association').setOptions({
extraParams: {
excludeIds : getAssociationsIds()
}
});
};
//function to exclude a product if it exists in the list
var getAssociationsIds = function()
{
if ($('#inputMyAssociations').val() === undefined)
return '';
return $('#inputMyAssociations').val().replace(/\-/g,',');
}
//function to add a new association, adds it in the hidden input and also as a visible div, with a button to delete the association any time.
var addAssociation = function(event, data, formatted)
{
if (data == null)
return false;
var productId = data[1];
var productName = data[0];
var $divAccessories = $('#divCrossSellers');
var $inputAccessories = $('#inputMyAssociations');
var $nameAccessories = $('#nameMyAssociations');
/* delete product from select + add product line to the div, input_name, input_ids elements */
$divAccessories.html($divAccessories.html() + '<div class="form-control-static"><button type="button" class="delAssociation btn btn-default" name="' + productId + '"><i class="icon-remove text-danger"></i></button> '+ productName +'</div>');
$nameAccessories.val($nameAccessories.val() + productName + '¤');
$inputAccessories.val($inputAccessories.val() + productId + '-');
$('#product_autocomplete_input_association').val('');
$('#product_autocomplete_input_association').setOptions({
extraParams: {excludeIds : getAssociationsIds()}
});
};
//the function to delete an associations, delete it from both the hidden inputs and the visible div list.
var delAssociations = function(id)
{
var div = getE('divMyAssociations');
var input = getE('inputMyAssociations');
var name = getE('nameMyAssociations');
// Cut hidden fields in array
var inputCut = input.value.split('-');
var nameCut = name.value.split('¤');
if (inputCut.length != nameCut.length)
return alert('Bad size');
// Reset all hidden fields
input.value = '';
name.value = '';
div.innerHTML = '';
for (i in inputCut)
{
// If empty, error, next
if (!inputCut[i] || !nameCut[i])
continue ;
// Add to hidden fields no selected products OR add to select field selected product
if (inputCut[i] != id)
{
input.value += inputCut[i] + '-';
name.value += nameCut[i] + '¤';
div.innerHTML += '<div class="form-control-static"><button type="button" class="delAssociation btn btn-default" name="' + inputCut[i] +'"><i class="icon-remove text-danger"></i></button> ' + nameCut[i] + '</div>';
}
else
$('#selectAssociation').append('<option selected="selected" value="' + inputCut[i] + '-' + nameCut[i] + '">' + inputCut[i] + ' - ' + nameCut[i] + '</option>');
}
$('#product_autocomplete_input_association').setOptions({
extraParams: {excludeIds : getAssociationsIds()}
});
};
//finally initialize the function we have written above and create all the binds.
initMyAssociationsAutocomplete();
//live delegation of the deletion button to our delete function, this will allow us to delete also any element added after the dom creation with the ajax autocomplete.
$('#divMyAssociations').delegate('.delAssociation', 'click', function(){
delAssociations($(this).attr('name'));
});
});
4) now you just need to save the associations made by your module autocomplete, and i suggest to perform it by first deleting any association made on a given product and then saving all of them. so you don't have to care about inserting or updating an entry
public function getContent(){
//post process part
if(Tools::isSubmit('saveMyAssociations'){
$product_id = (int)Tools::getValue('productId');
// see the function below, a simple query to delete all the associations on a product
$this->deleteMyAssociations($product_id);
if ($associations = Tools::getValue('inputMyAssociations'))
{
$associations_id = array_unique(explode('-', $associations));
if (count($associations_id))
{
array_pop($associations_id);
//insert all the association we have made.
$this->changeMyAssociations($associations_id, $product_id);
}
}
}
}
protected function deleteMyAssociations($product_id){
return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'my_associations` WHERE `id_product_1` = '.(int)$product_id);
}
protected function changeMyAssociations($associations_id, $product_id){
foreach ($associations_id as $id_product_2)
Db::getInstance()->insert('my_associations', array(
'id_product_1' => (int)$product_id,
'id_product_2' => (int)$id_product_2
));
}
I hope it can help you to go through all of this.

Ember.js input fields

Is it possible to use standard HTML5 input fields in an Ember.js view, or are you forced to use the limited selection of built in fields like Ember.TextField, Ember.CheckBox, Ember.TextArea, and Ember.select? I can't seem to figure out how to bind the input values to the views without using the built in views like:
Input: {{view Ember.TextField valueBinding="objectValue" }}
Specifically, I'm in need of a numeric field. Any suggestions?
EDIT: This is now out of date you can achieve everything above with the following:
{{input value=objectValue type="number" min="2"}}
Outdated answer
You can just specify the type for a TextField
Input: {{view Ember.TextField valueBinding="objectValue" type="number"}}
If you want to access the extra attributes of a number field, you can just subclass Ember.TextField.
App.NumberField = Ember.TextField.extend({
type: 'number',
attributeBindings: ['min', 'max', 'step']
})
Input: {{view App.NumberField valueBinding="objectValue" min="1"}}
#Bradley Priest's answer above is correct, adding type=number does work. I found out however that you need to add some attributes to the Ember.TextField object if you need decimal numbers input or want to specify min/max input values. I just extended Ember.TextField to add some attributes to the field:
//Add a number field
App.NumberField = Ember.TextField.extend({
attributeBindings: ['name', 'min', 'max', 'step']
});
In the template:
{{view App.NumberField type="number" valueBinding="view.myValue" min="0.0" max="1.0" step="0.01" }}
et voile!
Here is my well typed take on it :
App.NumberField = Ember.TextField.extend({
type: 'number',
attributeBindings: ['min', 'max', 'step'],
numericValue: function (key, v) {
if (arguments.length === 1)
return parseFloat(this.get('value'));
else
this.set('value', v !== undefined ? v+'' : '');
}.property('value')
});
I use it that way:
{{view App.NumberField type="number" numericValueBinding="prop" min="0.0" max="1.0" step="0.01" }}
The other systems where propagating strings into number typed fields.
You may also wish to prevent people from typing any old letters in there:
App.NumberField = App.TextField.extend({
type: 'number',
attributeBindings: ['min', 'max', 'step'],
numbericValue : function (key,v) {
if (arguments.length === 1)
return parseFloat(this.get('value'));
else
this.set('value', v !== undefined ? v+'' : '');
}.property('value'),
didInsertElement: function() {
this.$().keypress(function(key) {
if((key.charCode!=46)&&(key.charCode!=45)&&(key.charCode < 48 || key.charCode > 57)) return false;
})
}
})
Credit where its due: I extended nraynaud's answer
This is how I would do this now (currently Ember 1.6-beta5) using components (using the ideas from #nraynaud & #nont):
App.NumberFieldComponent = Ember.TextField.extend
tagName: "input"
type: "number"
numericValue: ((key, value) ->
if arguments.length is 1
parseFloat #get "value"
else
#set "value", (if value isnt undefined then "#{value}" else "")
).property "value"
didInsertElement: ->
#$().keypress (key) ->
false if (key.charCode isnt 46) and (key.charCode isnt 45) and (key.charCode < 48 or key.charCode > 57)
Then, to include it in a template:
number-field numericValue=someProperty