AngularJS ng-repeat splicing array not updating DOM correctly - arrays

I'm running into an issue when using ng-repeat to create buttons from an array that is populated through a drag/drop system.
Here is a screenshot of the page. You're suppose to use the Numbers and Symbols to create an expression in the dropzone (gray area). In the screen shot I have a pre tag that is displaying the contents of the array. There is only one object in that array but the repeat is still displaying two. Is there something I'm not understanding about how ng-repeat handles itself when its array is updated?
$scope.arr = [];
//gets called when a number or symbol is dropped
$scope.dropItem = function(draggable, dropzone){
if(dropzone) {
var obj = {
name: draggable.name,
fragmentId: draggable.fragmentId,
controlId: draggable.controlId,
id: hal.utils.generateGuid()
};
$scope.arr.push(obj);
$scope.$apply();
}
};
//callback for draggables inside dropzone
$scope.reorderItem = function(draggable, dropzone){
//reorder
if(dropzone){
}
//remove
else {
for(var i = 0; i < $scope.arr.length; i++){
if($scope.arr[i].id == draggable.value){
$scope.arr.splice(i, 1);
$scope.$apply();
}
}
}
};
HTML
<td class="equation-drop-zone" hal-dropzone="PerimeterEquation" max="10">
<pre>{{arr}}</pre>
<button
ng-repeat="button in arr track by button.id"
hal-draggable="{{button.name}}"
value="{{button.id}}"
hal-text
control-id="{{button.controlId}}"
fragment-id="{{button.fragmentId}}"
drop-callback="reorderItem"
></button>
</td>

Related

Copy buttons element Array values - Javascript

I'm creating a dropdown list that sets and resets colors of buttons nearby. I'm trying to copy an array html button elements into another array. The problem is, When I change the first array of elements(color, wrapping etc..) the copy also gets changed. Here's my code:
var buttons = document.getElementsByTagName('button');
var copyallbuttons = [];
for (let i=0; i<buttons.length; i++)
{
let x = buttons[i].style.backgroundColor;
copyallbuttons[i] = x;
}
But when I make changes to buttons, even copyallbuttons gets changed. Please let me know how to copy values of html elements into another array (without reference) so that changing one doesnt affect other. This is basically for the "reset" button on the front end to reload all old styles of the elements.
var buttons = document.getElementsByClassName('changeColour');
for (let i=0; i<buttons.length; i++)
{
buttons[i].style.backgroundColor ="red";
}
<button class="changeColour" >Button 1</button>
<button class="changeColour" >Button 2</button>
<button class="" >Button 3</button>
You can use var buttons = document.getElementsByClassName("example");
and give common class name for buttons which you want to change colour.
function changeColour(colour) {
var buttons = document.getElementsByClassName('changeColour');
for (let i=0; i<buttons.length; i++)
{
buttons[i].style.backgroundColor = colour;
}
}
$(".changeColour").click(function(){
changeColour("red");
});
$(".resetBtn").click(function(){
changeColour("black"); //here default colour
});

Angular ng-repeat not updating after splice

I am working on a Angular Material site and using ng-repeat to populate a table. The list generated by the ng-repeat contains a list of users (the users array is populated by a RESTFul query).
Each "item-content" of the ng-repeat has a "Delete" button to delete/splice the item/user out of the array.
If I have no filters applied to the ng-repeat statement...when I splice the index of the selected user out of the array....the UI/DOM is updated and the user is removed from the list.
If I apply a filter (filters by the first letter of the last name and only shows those results)...when I splice the index out of the array...the UI/DOM is NOT updated and user remains in the list (from what I can tell from walking through the delete function from the console...the array is updated and the index/user is removed).
I filter being used is a custom angular filter and takes one input (the starting letter of the last name you want to filter on).
app.filter('startsWith', function () {
return function (items, letterMatch) {
var re = new RegExp("^[" + letterMatch.toUpperCase() + letterMatch.toLowerCase() + "]$");
var filtered = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
var lastNameLetter = item.user.name.substr(item.user.name.indexOf(' ') + 1, 1);
if (re.test(lastNameLetter)) {
filtered.push(item);
}
}
return filtered;
};
});
Here is the ng-repeat statement:
<md-item ng-repeat="user in ddUsers | startsWith:selectedFilter | startFrom:currentPage*pageSize | limitTo:pageSize">
In the controller...I am using Dialog Prompt (part of the Angular Material 1.1 RC framework) to initiate/confirm the Delete and proceed with the splice.
$scope.showPrompt = function(ev,index,user) {
// Appending dialog to document.body to cover sidenav in docs app
var confirm = $mdDialog.prompt()
.title('Delete User?')
.textContent('Type in DELETE to confirm deletion of the user.')
.placeholder('DELETE')
.ariaLabel('Delete User')
.targetEvent(ev)
.ok('Delete')
.cancel('Cancel');
$mdDialog.show(confirm).then(function(result) {
if(result=="DELETE")
{
$scope.ddUsers.splice(index, 1);
$scope.showSimpleToast('Successfully deleted ' + user.user.name);
}
else
{
$scope.showSimpleToast('Did not confirm properly');
}
}, function() {
$scope.showSimpleToast('Cancelled deletion for ' + user.user.name);
//var message = $scope.filterLastName(user.user.name);
//$scope.showSimpleToast(message);
});};
When the controller loads...I have a init function that executes and populates ddUsers from a custom angular factory:
var init = function () {
$scope.ddUsers = $UserList.UserList;
}
init();
I am not sure why ng-repeat is not updating after the splice when a filter is applied. If I have no filter on the ng-repeat, ng-repeat does update the UI/DOM to reflect the change and you don't see the user anymore.
While it is not shown how you call the function I assume you are passing in $index from view.
The problem with doing that is that $index will not be the same index for filtered array as for the original array so you would be splicing the wrong element out of the main data array
You need to do your own indexing by passing in the actual object.
I will also assume that user is that object so you would do:
if(result=="DELETE")
{
var idx = $scope.ddUsers.indexOf(user);
if(idx > -1 ){// make sure can index this object
$scope.ddUsers.splice(idx, 1);
$scope.showSimpleToast('Successfully deleted ' + user.user.name);
else{
// Ooops ... can't find it
}
}

Cannot getText from list

I am trying to getText but forever reason it is coming up empty. The text is a readonly field called theater and then a list of 28 numbers underneath it. I have tried many different ways of obtaining the text, including getAttribute which returns null. I have a scroll down function that scrolls down the page so I can see that it is recognized by the selector as it is scrolling and counting. I'm really lost on this one. I have an array called theaterArray that matches the text line by line, however cannot do this.
the html looks like this:
<td class="capacity" title="Theater" data-style="theater">
<input class="readonly" type="text" data-bind="value: capacityObservables.theater, readonly: $root.editItem() !== $data || enhanced, valueUpdate: 'afterkeydown'" readonly="readonly">
</td>
what I have for my code:
//I have tried using the data-bind but i get an invalid selector error
var theaterStyle = element.all(by.css('[data-style="theater"]'));
theaterStyle.count().then(function(count) {
console.log(count);
j = 0;
for (var i = 0; i < count; i++) {
//scrolls down the list element by element
browser.executeScript("arguments[0].scrollIntoView();", theaterStyle.get(i).getWebElement());
theaterStyle.get(i).getText().then(function(text) {
console.log(text, theaterArray[j], j);
expect(text).toEqual(theaterArray[j++]);
});
}
});
Perhaps text is stuffed in the value attribute. Did you try this,
element.all(by.css('td[title="Theater"]>input')).each(function(element, index) {
element.getAttribute('value').then(function (text) {
console.log(index, text);
expect(theaterArray.includes(text)).toEqual(true);
});
});

Repeat an item in ng-repeat

I have an array of objects whereby there is a need to allow the user to add an item that is a duplicate of another. It is triggered when they increase the quantity where a property called 'HasPers' is true.
The HTML markup looks like:
<td width="10%"><input type="number" value="{{productdetails.Quantity}}" ng-model="productdetails.Quantity" min="1" ng-change="change(productdetails)" /></td>
and the function is:
$scope.change = function (item) {
if (item.HasPers) {
item.Quantity = 1;
$scope.items.push(item);
}
};
Initially this had a problem in the repeater wherby it knew it was a duplicat object but this was solved by adding 'track by $index' in the repeater
<tr ng-repeat="productdetails in items track by $index">
http://mutablethought.com/2013/04/25/angular-js-ng-repeat-no-longer-allowing-duplicates/
but the item is clearly still associated with the original since when I change some properties on the original one they change on the duplicate one too. I really want to avoid typing out all of the properties again as in
How to add new key in existing array in angular js?
is there any way to create a new item that is identical to the one being passed in but is a different object?
try
$scope.change = function (item) {
if (item.HasPers) {
var copyItem=angular.copy(item);
copyItem.Quantity = 1;
$scope.items.push(copyItem);
}
};
Try by using angular.copy
$scope.change = function (item) {
if (item.HasPers) {
var newItem = angular.copy(item);
newItem.Quantity = 1;
$scope.items.push(newItem);
}
};

How can I get ng-click to function with ng-repeat and ng-bind-html?

I am trying to get a ng-click directive to function within an ng-repeat and ng-bind-html. The ng-click code is added to a string of html from data pulled from the server (hence the ng-bind-html). The setup has a controller, a base template that is put onto the page with Drupal, and a partial that is loaded via the template from Drupal.
The controller looks like this at the moment:
var guideListController = angular.module('app')
.controller('guideListController', [
'$scope',
'$sce',
'$compile',
'ViewService',
'ToolKit',
function($scope, $sce, $compile, ViewService, ToolKit) {
// Storage for which rows need more/less links
this.rowIndex = [];
this.showFull = false;
this.showFullClick = function() {
alert('Showfull');
};
this.trustIntro = function(code) {
return $sce.trustAsHtml(code);
};
// Fetch the guide list view from services
var data = ViewService.get({view_endpoint:'guide-service', view_path: 'guide-list'}, function(data) {
//console.log(data);
// Update/process results
for (var row in data.results) {
// Create short intro w/ truncate.js
data.results[row].Intro_short = $sce.trustAsHtml($scope.guideList.getShortIntro(data.results[row], row));
//data.results[row].Intro_short = $scope.guideList.getShortIntro(data.results[row], row);
// Update intro
data.results[row].Introduction = $sce.trustAsHtml($scope.guideList.updateIntro(data.results[row], row));
//data.results[row].Introduction = $scope.guideList.updateIntro(data.results[row], row);
}
$scope.guideList.guides = data.results;
});
// Add a read less anchor tag at the end of the main intro
this.updateIntro = function(row, row_index) {
var intro = row['Introduction'].trim();
if ($scope.guideList.rowIndex[row_index]) { // only apply Less link if needed
var index = intro.length - 1;
var tag = [];
if (intro.charAt(index) === '>') { // we have a tag at the end
index--;
do {
tag.push(intro.charAt(index));
index--;
} while (intro.charAt(index) != '/'); // the closing tag
index--; // we move the index one more for the "<"
tag.reverse(); // Reverse
tag = tag.join('');
}
var inserts = ['div', 'p']; // we insert the Less link here.
if (jQuery.inArray(tag, inserts) >= 0) { // insert into the tag
intro = intro.substr(0, index) + ' <a class="less" ng-click="$parent.guideList.showFull = false">Less</a>' + intro.substr(index);
}
else { // insert at the end of the html
intro = intro + '<a class="less" ng-click="this.showFull = false">Less</a>';
}
}
return intro;
};
// Truncate the long intro into a shorter length blurb
this.getShortIntro = function(row, row_index) {
// Truncate if necc.
var short_intro = jQuery.truncate(row['Introduction'], {
length: 250,
words: true,
ellipsis: '\u2026 <a class="more moreish" attr-ng-click="guideList.showFullClick()">Read on</a>'
});
var more = jQuery('.more', short_intro); // select more link
if (more.length) { // do we have a more link
$scope.guideList.rowIndex[row_index] = true;
}
else { // no more link
$scope.guideList.rowIndex[row_index] = false;
}
$compile(short_intro)($scope);
return short_intro;
};
}]);
As you can see in the ViewService.get() call, data is fetched and then processed. The processing simply involves putting a link at the end of the "Intro" field that is intended to be clickable.
For a while I was having a tough time to even get the ng-click directive to even show (it was being filtered out w/out $sce.trustAsHtml). Now it is there but clicking it has no effect.
The main template (from Drupal) currently looks like:
<div class="guide-listing" ng-controller="guideListController as guideList">
<a ng-click="guideList.showFullClick()">Click me</a>
<div class="guide-teaser"
ng-repeat="guide in guideList.guides"
ng-include src="'/sites/all/themes/ngTheme/ngApp/partials/guide_teaser.html'">
<!-- See guide_teaser.html partial for guide teasers -->
</div>
</div>
The ng-click as placed in the Drupal template above works as expected.
And for the partial that is used in the ng-repeat, it looks like so:
<div ng-controller="guideListController as guideList">
<h2 class="guide-teaser-title">{{guide.node_title}}</h2>
<div class="guide-teaser-intro" ng-bind-html="guide.Introduction" ng-show="guide.showFull">
{{guide.Introduction}}
</div>
<div class="guide-teaser-intro-short" ng-bind-html="guide.Intro_short" ng-show="!guide.showFull">
{{guide.Intro_short}}
</div>
</div>
So far I have only been working on getting the ng-click to work on the short_intro and have had no success so far. Any ideas as to what I am doing wrong would be greatly appreciated!
Ok, So I did get some traction! I used the ngHtmlCompile (http://ngmodules.org/modules/ng-html-compile) directive that was created by https://github.com/francisbouvier (thanks!)
The issue was that the new (dynamic) html wasn't being compiled.
At first it didn't work. I had two issues that prevented it from firing:
A: I stopped using $sce.trustAsHtml. Using this in conjunction with the directive caused the content to disappear!
B: The other issue was one of scope. After I changed the directive such that transclude was set to false it worked just fine!

Resources