Aurelia not displaying correct representation of array after removing item - data-binding

I have a list of items in an array.
When i click the item in my view i am attempting to remove this item
View
<div class="lootItem" repeat.for="lootItem of stack">
<div class="noselect" click.delegate="$parent.takeItem(lootItem)">
<i class="fa fa-diamond"></i> ${lootItem.value}
</div>
</div>
ViewModel
takeItem(lootItem){
this.eventAggregator.publish(new ItemTaken(lootItem));
console.log(_.includes(this.stack, lootItem)); //true
_.pull(this.stack, lootItem); //removes item and fails to update the ui
_.remove(this.stack, lootItem); //removes item and fails to update the ui
this.stack.shift(); //removes first item and updates the ui
}
Both .pull() and .remove() (using lodash) will remove the item in the array but not update the ui.
.shift() manages to remove an item from the array and is updating the UI.
Why is Aurelia not updating the UI despite me removing the item when using lodash?
addendum: it might be worth noting if i click the same item twice then _.includes is true the first time and then false the second time.

Aurelia can provide you with the index of the current item when inside a repeater using the $index variable. Then, simply use the built in array methods that ES2015 provides:
View
<div class="lootItem" repeat.for="lootItem of stack">
<div class="noselect" click.delegate="$parent.takeItem($index, lootItem)">
<i class="fa fa-diamond"></i> ${lootItem.value}
</div>
</div>
ViewModel
takeItem($index, lootItem){
this.eventAggregator.publish(new ItemTaken(lootItem));
this.stack.splice($index, 1);
}

I believe your problem lies in the scope of the variables.
_.pull and _.remove are returning a new instance of an array.
Try
this.stack = _.remove(this.stack, lootItem);

Using ES2015 Sets Yet another approach is to use the ES2015 Set data
structure. It can be initialized with an array of values, and the Set
prototype even provides a delete method for removing a specific value
...
However, the semantics of sets are different from regular arrays. By
definition, a set is a collection of unique values. Therefore, it
never contains duplicates. If you need a collection that allows for
duplicate values, sets are the wrong data structure.
https://blog.mariusschulz.com/2016/07/16/removing-elements-from-javascript-arrays
I am now using a Set for this and it allows me to use .add() and .delete(). I just need to remember that everything has to be unique (okay in my case here)
I'd still like to understand why manipulating the array though lodash doesn't work but i'll investigate that some other time.
Addendum
You can also use a prototype:
interface Array<T> {
remove(itemToRemove: T): Array<T>;
}
(<any>Array.prototype).remove = function (itemToRemove) {
const index = this.indexOf(itemToRemove);
if (index !== -1) {
this.splice(index, 1);
}
return this;
}

Related

How do you use element attributes with Svelte when compiling to a custom element?

I am trying to create a custom checkbox using Svelte and compile it to a custom element (with customElement: true) but I don't know what way is the proper way of using attributes.
Looking at the generated code it looks like the attributeChangedCallback and observedAttributes are generated automatically depending on what variables you export in the .svelte file so that seems to work as expected though I haven't found any documentation for this.
Right now I have this hacky approach of creating the checked property manually and managing the different values of the checked attribute (to match native behavior) as seen here:
<script>
import { onMount } from 'svelte';
export let checked;
onMount(() => {
if (checked != null) {
checked = true;
} else {
checked = false;
}
});
</script>
I then simply set the checked attribute to the input element and add a change event to keep the property updated when checking/unchecking the input:
<input
on:change={(event) => {
checked = event.currentTarget.checked;
}}
type="checkbox"
{checked}
/>
Is this how you are meant to use attributes and managing property states with Svelte and custom elements?
While that works, Svelte allows you to simplify it quite a bit:
<svelte:options tag="my-checkbox"/>
<script>
// `checked` will be `false` by default, but a user may pass in a
// different value through the attribute/property with the same name.
export let checked = false;
// this next line isn't strictly necessary. if you want to guarantee
// `checked` is always a boolean even if the user passes a different
// value, you can use a reactive statement to consistently convert
// `checked` into a boolean. this will re-run automatically.
$: checked = (checked !== false);
</script>
<!--
`bind:checked` is a shorthand for `bind:checked={checked}`, which in
turn means the `checked` property of the input will have a two-way
binding to the `checked` property of this component. updating one will
automatically update the other.
-->
<input type="checkbox" bind:checked>
Svelte's reactivity system will ensure the component's checked property/variable is kept in sync with the custom element's checked property, and as you noted, changes to the custom element's attribute will also trigger an update to the component.
Do note that you won't see the changes in the component reflected in the element's attributes, so you'll have to do that manually if necessary. Usually the property access is enough, though.

Removing items from lists does not refresh correct components

I have a Blazor page which display a list of components based on a list on which I dynamically add/remove items :
<TelerikButton OnClick="() => State.AddSymbols(SymbolToAdd)">Add</TelerikButton>
#foreach (var symbolToDisplay in State.SymbolsToDisplay)
{
<StockGraph SymbolToDisplay="#symbolToDisplay" OnRemoved="() => State.RemoveSymbol(symbolToDisplay)"/>
}
This is the code of the state :
public void AddSymbols(string symbol)
{
SymbolsToDisplay.Add(symbol);
StateHasChanged();
}
public void RemoveSymbol(string symbol)
{
SymbolsToDisplay.Remove(symbol);
StateHasChanged();
}
And the code of the component :
<div class="stock-graph">
<p>
#companyData?.companyName
</p>
<div>
<TelerikButton class="delete-item" onclick="#OnRemoved">X</TelerikButton>
</div>
<TelerikChart ref="#chart">
<TelerikChartSeriesItems>
<TelerikChartSeries Type="ChartSeriesType.Line" Data="#data.StockValues"
Field="#nameof(StockValueData.StockValue)"
CategoryField="#nameof(StockValueData.Date)">
</TelerikChartSeries>
</TelerikChartSeriesItems>
<TelerikChartValueAxes>
<TelerikChartValueAxis Color="red"></TelerikChartValueAxis>
</TelerikChartValueAxes>
</TelerikChart>
This is supposed to be pretty straightforward. But for some reason when deleting a component it is always the last component displayed that is deleted even if the list has the required value removed and contains all the correct values.
I have a example of this code running on my github : GitHubRepo
Just add random Stock symbols like GOOG, AAPL, ACC
So it turns out that the issue was coming from the way I way initializing/updating the parameter of my component.
OnInitAsync is not run again after you require to update the data in the page and component.
Instead you have to use OnParametersSetAsync in order for the component to update its parameter correctly

How to Attach Events to Table Checkboxes in Material Design Lite

When you create a MDL table, one of the options is to apply the class 'mdl-data-table--selectable'. When MDL renders the table an extra column is inserted to the left of your specified columns which contains checkboxes which allow you to select specific rows for actions. For my application, I need to be able to process some JavaScript when a person checks or unchecks a box. So far I have been unable to do this.
The problem is that you don't directly specify the checkbox controls, they are inserted when MDL upgrades the entire table. With other MDL components, for instance a button, I can put an onclick event on the button itself as I'm specifying it with an HTML button tag.
Attempts to put the onclick on the various container objects and spans created to render the checkboxes has been unsuccessful. The events I attach don't seem to fire. The closest I've come is attaching events to the TR and then iterating through the checkboxes to assess their state.
Here's the markup generated by MDL for a single checkbox cell:
<td>
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect mdl-data-table__select mdl-js-ripple-effect--ignore-events is-upgraded" data-upgraded=",MaterialCheckbox">
<input type="checkbox" class="mdl-checkbox__input">
<span class="mdl-checkbox__focus-helper"></span>
<span class="mdl-checkbox__box-outline">
<span class="mdl-checkbox__tick-outline"></span>
</span>
<span class="mdl-checkbox__ripple-container mdl-js-ripple-effect mdl-ripple--center">
<span class="mdl-ripple"></span>
</span>
</label>
</td>
None of this markup was specified by me, thus I can't simply add an onclick attribute to a tag.
If there an event chain I can hook into? I want to do it the way the coders intended.
It's not the nicest piece of code, but then again, MDL is not the nicest library out there. Actually, it's pretty ugly.
That aside, about my code now: the code will bind on a click event on document root that originated from an element with class mdl-checkbox.
The first problem: the event triggers twice. For that I used a piece of code from Underscore.js / David Walsh that will debounce the function call on click (if the function executes more than once in a 250ms interval, it will only be called once).
The second problem: the click events happens before the MDL updates the is-checked class of the select box, but we can asume the click changed the state of the checkbox since last time, so negating the hasClass on click is a pretty safe bet in determining the checked state in most cases.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
$(document).on("click", ".mdl-checkbox", debounce(function (e) {
var isChecked = !$(this).hasClass("is-checked");
console.log(isChecked);
}, 250, true));
Hope it helps ;)
We currently don't have a way directly to figure this out. We are looking into adding events with V1.1 which can be subscribed to at Issue 1210. Remember, just subscribe to the issue using the button on the right hand column. We don't need a bunch of +1's and other unproductive comments flying around.
One way to hack it is to bind an event to the table itself listening to any "change" events. Then you can go up the chain from the event's target to get the table row and then grab the data you need from there.
You could delegate the change event from the containing form.
For example
var form = document.querySelector('form');
form.addEventListener('change', function(e) {
if (!e.target.tagName === 'input' ||
e.target.getAttribute('type') !== 'checkbox') {
return;
}
console.log("checked?" + e.target.checked);
});

Angular: How to bind to an entire object using ng-repeat

I'm just beginning to experiment in Angular, and confused about how best to approach binding using ng-repeat. I basically get the point about ng-repeat creating a child scope. My problem is much more basic :) For html like this:
<div ng-controller="swatchCtrl" class="swatch-panel">
Swatches
<ul>
<li ng-repeat="swatch in swatchArray" class="swatch">
<input
type="radio"
name="swatches"
ng-model="$parent.currentSwatch"
value="{{swatch}}"
>
<label class="swatch-label">
<div class="swatch-color" style="background-color: #{{swatch.hexvalue}};"></div
><span class="swatch-name">{{swatch.colorName}}</span>
</label>
</li>
</ul>
currentSwatch is:
<pre>{{currentSwatch | json}}</pre>
currentSwatchObj is:
<pre>{{currentSwatchObj | json}}</pre>
how do I tell this to fire??
swatchArray is:
<pre>{{swatchArray | json}}</pre>
</div>
and javascript like this:
function swatchCtrl($scope) {
$scope.swatchArray = [
{colorName:'Red', hexvalue: 'ff0000', selected: 'false'},
{colorName:'Green', hexvalue: '00ff00', selected: 'false'},
{colorName:'Blue', hexvalue: '0000ff', selected: 'false'}
];
$scope.currentSwatch = {};
}
http://jsfiddle.net/8VWnm/
I want to:
a) When the user clicks on a radio button, I want it to set both the colorName and the hexvalue properties of the currentSwatch object. Right now the binding seems to be giving me a stringified object from the array. How do watch the return of currentSwatch so I can parse it back to an available object? Simple, I know, but what am I missing?
b) When the user clicks on a radio button, I think I want that to set the value of the corresponding "selected" key in the original array to "true". Vice versa for unchecking. Let's say that only one swatch can ever be selected at a time in the palette. (I would like in theory to be able to iterate through the array later on, on the supposition that the different keys and values are likely to sometimes not be unique.)
This kinda stuff is super easy with jquery methods, but I'd like to learn the idiomatic angular way. Thanks in advance for any help.
http://jsfiddle.net/8VWnm/54/
Instead of listening to the ng-click event I would set the index of the selected element to a variable called "currentSwatchIndex"
<li ng-repeat="swatch in swatchArray" class="swatch">
<input
type="radio"
ng-model="$parent.currentSwatchIndex"
value="{{$index}}"
>
</li>
The you can $watch value changes of the currentSwatchIndex in your controller and set the selected swatch-Object and selection states in this $watch function:
$scope.$watch('currentSwatchIndex', function(newValue, oldValue) {
$scope.currentSwatchObj = $scope.swatchArray[newValue];
$scope.swatchArray[newValue].selected = true;
$scope.swatchArray[oldValue].selected = false;
});
Only knowing the currentSwatchIndex should be enough to identify the selected swatchObject. So probably you can get rid of the currentSwatchObj and the selected property of your swatchArray.
You can always get the selected swatch programmatically through a array access.
For future users that can come here to do the same in a select, you don't need use any index, the select must be done like this:
http://docs.angularjs.org/api/ng.directive:select

How to make an assertion based on class name in web driver

The HTML is as follows:
<section class="my-account-box-element">
<span class="item icon-home icon-color-1"></span>
</section>
Need to make an assertion based on the class name which changes from 'icon-home icon-color-1' to 'icon-home icon-color-0' depending upon the condition.
First i must warn that you can't add two classes when searching for it using the By, since 'item', 'item-home' on your example are the same. I'm going to assume the main difference is in 'item-color-1' and 'item-color-0'.
WebElement myAccountBoxElement = driver.findElement(By.className("my-account-box-element"));
WebElement spanItem = myAccountBoxElement.findElement(By.tagName("span"));
boolean itemColor = (spanItem.getAttribute("class").contains("item-color-1")) ? true : false;
if (itemColor) {
// do stuff for item-color-1 element
}
// do stuff for the item-color-0 element
}
The above code should work flawlessly provided the above code is the actual HTML, if there are more tags, use findElements() instead and loop in it.
Also i went with a ternary if since it keeps a cleaner code, providing you are only working with those two elements
change as per your needs
assertTrue(driver.findElements( By.Xpath(".//span[contains(#class,'icon-color-1')]")).size() != 0)
Can also use this
By.className("classname");
boolean isElementPresent = driver.findElement(By.className("classname"));

Resources