Unselect effect Radio Material2 Angular 5 - angular5

I'm using mat-radio-button(material2) and I have something like a quiz. When I select an answer and go to the next quiz, the visual effect of unselect is showing in the second quiz. This happens if the selected option in the first quiz, appears in the second too, in the same position or after.
I made a simple example. You can reproduce it selecting "aa", then click ">>" button.
HTML
<mat-radio-group class="example-radio-group" [(ngModel)]="radioSelected">
<mat-radio-button *ngFor="let option of tempSublist" [value]="option" class="example-radio-button" color="primary">
{{option}}
</mat-radio-button>
</mat-radio-group>
<hr>
<button (click)="prev()"><<</button>
<button (click)="next()">>></button>
Typescript
list = [
['aa','bb','cc'],
['cc','aa','bb'],
['bb','cc','ee'],
['dd','bb','cc'],
];
index = 0;
tempSublist = this.list[this.index];
radioSelected:string;
next(){
if(this.index < this.list.length){
this.radioSelected = null;
this.index++;
this.tempSublist = this.list[this.index];
}
}
prev(){
if(this.index >= 1){
this.radioSelected = null;
this.index--;
this.tempSublist = this.list[this.index];
}
}
https://stackblitz.com/edit/angular-7s9qvs?file=app%2Fradio-ng-model-example.html
How can I solve this?

Adding trackBy option on ngFor helps to reduce the transition between the two states. Also disabling repples with [disableRipple]="true" seems to reduce the visual effect:
HTML
<mat-radio-button [disableRipple]="true" *ngFor="let option of tempSublist;
trackBy: trackByFn" ...>
...
Typescript
next() {
if (this.index < this.list.length - 1) {
//add this ^
...
trackByFn(index, item) {
return index;
}
I thought adding a control on navigation buttons would be nice:
<button [disabled]="index <= 0" (click)="prev()"><<</button>
^- this
<button [disabled]="index === tempSublist.length" (click)="next()">>></button>
^- this
Demo
If this is not enough, in last recourse, hide and show the whole block.
Wrap it in a div and set ngIf:
<div *ngIf="isVisible">
<mat-radio-group class="example-radio-group" [(ngModel)]="radioSelected">
...
and use timeout in the class on button click:
next() {
isVisible = true;
this.isVisible = false;
if (this.index < this.list.length - 1) {
...
}
setTimeout(() => this.isVisible = true, 30)
}
Demo

Related

Vue 2 Moving Window with Keyboard Navigation

I have created a list in my program. In this list, you press ArrowUp or ArrowDown to navigate between the items that are generated with a v-for in said list. When you use the arrows to navigate the list, a counter is used to know which item inside this list is the "selected" one. My current problem is: When you "select" an item that is outside your view, the Browser's Window doesn't follow it down, allowing the selection to keep "going down" this list, but the user isn't able to see the other selected items.
I've tried messing around with the focus() function, but I don't know how to focus() a specific item without using the ref atribute, which I also can't seem to implement since it's being generated with v-for.
Here's my current table:
<table class="table table-striped p-0 m-0" v-show="pedidos.length > 0">
<thead class="table-dark">
<tr class="">
<th>Cliente Emissor</th>
<th>Produto</th>
<!-- <th>Endereço</th> -->
<!-- <th>Valor</th> -->
<th>Data Emissão</th>
<th>Estado</th>
</tr>
</thead>
<tbody>
<template v-for="(pedido, index) in pedidos">
<itemPedido :pedido="pedido" :key="index" :endereco="endereco" :class="[(navegacaoAtual == index) ? 'teste' : '']" ref="teste"></itemPedido>
<itemEdit #atualizarListaPedidos="atualizarListaPedidos" #atualizarStatusModal="atualizarStatusModal" class="animation-dropdown" :pedido="pedido" :key="pedido.nome"></itemEdit>
</template>
</tbody>
</table>
Here's the current navigateWithArrows() function I've made:
navegarSetas(key) {
if (this.navegacaoSetasAtiva == false) {
this.navegacaoSetasAtiva = true
this.navegacaoAtual = 0
} else if (this.modalAtivado == false && this.navegacaoSetasAtiva == true) {
if (key == 'ArrowDown' && this.navegacaoAtual < this.pedidos.length - 1) {
this.navegacaoAtual++
Vue.nextTick().then(() => {
let teste1 = this.$refs.teste[this.navegacaoAtual]
teste1.$el.focus()
})
} else if (key == 'ArrowUp' && this.navegacaoAtual <= this.pedidos.length && this.navegacaoAtual > 0) {
this.navegacaoAtual--
} else if (key == 'Enter') {
let pedidoSelecionado = this.pedidos[this.navegacaoAtual].id
Event.$emit('changeShow', pedidoSelecionado)
}
}
},
All help is appreciated. Thank you!
After taking a break and resuming my search, I stumbled upon something that did exactly what I wanted: the scrollIntoView() function.
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

v-html with conditional rendered

I'm learning Vue.js and I really love it, but I'm currently facing a problem.
I have this code in my template:
<button type="button" class="btn btn-warning ml-auto" v-if="steps.length - 1 == currentStep" #click="submitProject" v-html="paymentAmount">{{model.projectSelectedOptions.length < 1 ? 'Publish without option (free)' : paymentAmount }}</button>
And the paymentAmount computed:
paymentAmount() {
var amount = 0;
this.model.projectSelectedOptions.forEach(function(option) {
if (option == 1) {
option = 19.99
} else if (option == 2) {
option = 9.99
} else {
option = 7.99
}
amount += option;
});
return 'Next : payment (' + amount.toFixed(2) + ' € <span class="price-ht">HT</span>)';
}
My problem is that if I put v-html="paymentAmount" in my button, I never see "Publish without option (free)", just "Next : payment (0 € HT)".
If I remove v-html attribute, I can see "Publish without option (free)" but when I have selected some options, Vue.js render "Next : payment (0 € <span class="price-ht">HT</span>)" (so with raw span).
How should I do it?
EDIT:
For the moment, I added a button with a different v-if condition, but it would be cool if I can do it in one line of code, I don't like the duplicate 😅.
How about putting all your logic inside of your computed property?
HTML part:
<button type="button" class="btn btn-warning ml-auto" v-if="steps.length - 1 == currentStep" #click="submitProject" v-html="paymentAmount"></button>
Vue part:
paymentAmount() {
if(this.model.projectSelectedOptions.length < 1)
{
return 'Publish without option (free)';
}
else
{
var amount = 0;
this.model.projectSelectedOptions.forEach(function(option) {
if (option == 1) {
option = 19.99
} else if (option == 2) {
option = 9.99
} else {
option = 7.99
}
amount += option;
});
return 'Next : payment (' + amount.toFixed(2) + ' € <span class="price-ht">HT</span>)';
}
}
You might use a conditional span as button content:
<button
type="button"
class="btn btn-warning ml-auto"
v-if="steps.length - 1 == currentStep"
#click="submitProject">
<span v-if="model.projectSelectedOptions.length < 1">Publish without option (free)</span>
<span v-else v-html="paymentAmount"></span>
</button>

Dynamically Tally Child Elements by Classname in Vue

I have a page that allows a user to drag/drop images into pre-defined DIVs, then I tally up the total value of the images based on their class name. What I am trying to do is get vue to read the values from each outer div.answer and get the class names of the child images.
My source code is:
<div
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint"
></div>
<script>
Vue.component('box-answers', {
props: ['level','hint'],
template: '<div class="droppable answer :id="level" :title="hint"></div>'
});
new Vue({
el: '#mainapp',
data: {
boxes: [
{ id: 1, level: 'baselevel-1', hint: 'x 1' },
{ id: 2, level: 'baselevel-2', hint: 'x 20' },
{ id: 3, level: 'baselevel-3', hint: 'x 400' },
{ id: 4, level: 'baselevel-4', hint: 'x 8,000' },
{ id: 5, level: 'baselevel-5', hint: 'x 160,000' }
]
}
</script>
This converts to the follow HTML (the nested DIVs and SPANs are user-possible entries by dragging):
<div id="baselevel-5" class="droppable answer" title="x 160,000">
<div><img src="images/line.gif" alt="Five" class="imgfive"></div>
<span><img src="images/dot.gif" alt="One" class="imgone"></span>
</div>
...
<div id="baselevel-1" class="droppable answer" title="x 1">
<span><img src="images/line.gif" alt="One" class="imgone"></span>
</div>
Currently, I have jQuery/JavaScript calculating the point values using the following:
$(function(j) {
var arAnswers = Array(1);
count = 0; //
j("div.answer").each(function( idx ) {
currentId = j(this).attr('id');
ones = 0;
fives = 0;
if ( j("#" + currentId).children().length > 0 ) {
ones = j("#" + currentId).children().find("img.imgone").length * 1;
fives = j("#" + currentId).children().find("img.imgfive").length * 5;
arAnswers[count] = ones + fives; //Tally box value
count++;
}
});
});
I would like Vue to perform similar iteration and addition to return total value of ones and fives found based on the image classname.
Currently, you are approaching this problem as a pure-play DOM operation. If that is what you need then you can simply use $refs:
<!-- NOTICE ref -->
<div ref="boxAnswers"
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint">
</div>
Inside your high-level component, you will have a function like:
function calculate() {
// NOTICE $refs
const arAnswers = this.$refs.boxAnswers.map((x) => {
// $el is the DOM element
const once = x.$el.querySelectorAll('img.imgone').length * 1;
const fives = x.$el.querySelectorAll('img.imgfive').length * 5;
return once + fives
});
return arAnswers;
}
But this is not the correct Vue way of doing things. You have to think in terms of events and data model (MVVM - don't touch DOM. DOM is just a representation of your data model). Since, you have a drag-n-drop based application, you have to listen for drag, dragstart, dragend and other drag events. For example:
<!-- NOTICE drop event -->
<div #drop="onDropEnd(box, $event)"
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint">
</div>
Your onDropEnd event handler will look like:
function onDrop(box, $event) {
// box - on which box drop is happening
// $event.data - which image is being dropped
// Verify $event.data is actually the image you are intending
if ($event.data === 'some-type-image') {
// Do the counting manipulations here
// ... remaining code
}
}
This is not a complete code as I don't know other components. But it should help you with the required direction.

Check if value exists in vuejs

I have data : 1,2,3,4,4,5 & my code like this:
<div id="looping" v-for="display in editlistAssesments">
{{display.test_id}}
</div>
My code if in php such as like this
$temp_id = array();
foreach($data as $data){
if(in_array($data ->test_id,$temp_id)){
echo" 1: no";
echo" 2: no";
echo" 3: no";
echo" 4: yes"; //because he have same value
echo" 5: no";
$temp_id[] = $data ->test_id;
}
}
how I can do that in loop vueJs..??
From my point of view, the best way is:
<div id="looping"
v-for="display in editlistAssesments">
<span v-if="typeof display.test_id !== 'undefined'">
{{display.test_id}}
</span>
</div>
Because if you use v-if="display.test_id" and the test_id value is 0 (boolean comparation) you will never see the display.text_id.
You can use also this another condition to check if is null or undefined: display.test_id != null (loose equality operator)
As far as I understand, you want to check if value is in array and then render it accordingly?
If so, you need a Vue custom filter. Something like this will do the trick:
var vm = new Vue({
el: 'body',
data: {
editlistAssesments: [1,2,3,4,4,5]
},
filters: {
ifInArray: function (value) {
return this.editlistAssesments.indexOf(value) > -1 ? 'Yes' : 'No';
}
},
});
And then use it like this:
<div id="looping" v-for="display in editlistAssesments">
<span v-text="display.test_id | ifInArray"></span>
<!-- bind Vue value to html element is better practice -->
</div>
Check docs for more information:
http://vuejs.org/guide/custom-filter.html

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.