ngRepeat not updating even though list is - javascript

I'm having a problem where despite a list being updated, ngRepeat does not display the information.
In the code below, the $scope.selectedImages array is being added to when images are selected using the file input. It is set up so that only unique files will be added to the list of selectedImages (no duplicates).
At the end of the function, scope.selectedImages prints the array with the new values, but in the view, ngRepeat does not add the new list items with the image names.
Other questions regarding this issue said that $scope.$apply should fix the problem, but for me it is not. Am I using it wrong or is there something else that I am misunderstanding.
EDIT: View 1 and View 2 are in separate HTML pages that both rely on the same controller.
View 1:
<input type="file" multiple accept="image/*" class="file-input" id="img-upload-btn" onchange="angular.element(this).scope().select(this)">
<md-button class="md-primary md-raised sidenav-btn" ng-click="proxy('img-upload-btn')">
Select <strong>Images</strong>
</md-button>
The proxy function allows the md-button to click the input.
View 2:
<div layout-padding flex ng-controller="upload-controller">
<ul>
<li ng-repeat="im in selectedImages">{{im.name}}</li>
</ul>
</div>
Controller:
$scope.selectedImages = [];
$scope.select = function(element) {
$scope.$apply(function(scope) {
var justFiles = $.map(element.files, function(val, key) {
return val;
}, true);
var fileEquality = function(f1, f2) {
if (f1.name != f2.name) return false;
if (f1.size != f2.size) return false;
if (f1.type != f2.type) return false;
if (f1.lastModified != f2.lastModified) return false;
return true;
}
// inefficient, find better way later
for (i in justFiles) {
var contains = false;
var file = justFiles[i];
for (i in scope.selectedImages) {
if (fileEquality(file, scope.selectedImages[i])) {
contains = true;
break;
}
}
if (!contains) scope.selectedImages.push(file);
}
console.log(scope.selectedImages);
});
};

Your input field template and controller template have to different scopes. Use $rootScope.selectedImages in your case.

Related

AngularJS - run check against elements as they come back

I just followed this
JSFiddle example to create a little search box from an array object in my javascript. Now after some tweaking and research on search.object and filter:search:strict. Now that I have it filtering correctly, I modified a checkbox in the template that is checked upon loading the document and switched on or off based on a custom data attribute in the html that is updated by the json array.
How do I run this check once someone clears the search and the old items show back up again?
In the HTML I have this template
<div ng-repeat="o in objects | filter:search:strict | orderBy:'obj_name'" class="objectContainer">
<h3>{{o.obj_name}}</h3>
<label class="userToggleSwitch" >
<input class="userToggleInput" data-isactive="{{o.obj_isactive}}" type="checkbox">
<div class="slider round"></div>
</label>
</div>
In the JS
angular.element(document).ready(function(){
angular.module('searchApp',[])
.controller('searchCtrl', ['$scope','objHolder',function ($scope,objHolder) {
$scope.search = '';
$scope.objects = [];
$scope.objects = objHolder.getobjects();
}])
// fake service, substitute with your server call ($http)
.factory('objHolder',function(){
var objects = objList;
return {
getobjects : function(){
return objects;
}
};
});
});
The JS that modifies the items on document load is using jquery like this
$(document).ready(function(){
//console.log($('.userToggleInput'));
$.each($('.userToggleInput'), function(){
if($(this).data("isactive") == 1){$(this).attr('checked', true);}
if($(this).data("isactive") == 0){$(this).attr('checked', false);}
})
$('.userToggleInput').click(function(){
if ($(this).attr('checked') == undefined) {
// THIS IS WHERE WE WILL MAKE AN AJAX CALL TO A PHP CLASS
// TO REQUEST IF THE USER CAN BE TURNED ON - DUE TO LIC RESTRICTIONS
$(this).attr('checked',true);
} else {
$(this).attr('checked',false);
}
//console.log($(this).attr('checked'));
});
});
Created JS Fiddle to assist in Helping in this manner

Can I make one object have the same order as another object?

I know that the title of this question already doesn't make sense because objects are unordered by their nature. BUT, I think if you take a look at this screen shot that's linked here, it'll make more sense.
Picture of the two objects in my console.log
Here is what's happening. I am creating an object called $scope.gameConfigs, which is itself created from data I receive from a server call (a provisions object). This $scope.gameConfigs creates a group of dropdown menus.
Basically what I'm trying to do is make the dropdown menus display previously saved data IF the data exists. When saved data exists, the server returns an object that has all the saved data. This data is ordered numerically. My problem is, this saved data is not always ordered the same as the data in the $scope.gameConfigs I create, which results in blank fields appearing in my dropdowns.
If you look at the screenshot I linked to, you can see that the keys in $scope.gameConfigs (the object with line 30 written next to it) are Map, Server, and Mode.
The second object, which is the saved data returned from the server, is a group of objects that each have a name property (which is the name of the dropdown menu). If you take a look at that, the order is Server, Map, and Mode.
My question is, how can I make the Saved Data object copy the order of my $scope.gameConfigs object? Is that even possible? If not, what would be the best way to proceed?
Here is the HTML and the controller I have for my page/controller, too, if that helps at all:
<form class="form-horizontal" ng-controller="GamePreferenceCtrl">
<div ng-repeat="(name, section) in gameConfigs">
<label ng-bind="name"></label>
<select class="form-control" ng-model="formData.settings[$index].value" ng-change="dropdownItemClicked(name, formData.settings[$index].value)">
<option value="" disabled="disabled">---Please Select Option---</option>
<option ng-repeat="item in section" value="{{item.value}}" ng-bind="item.value"></option>
</select>
</div>
<div class="col-md-12" ng-include="gametemp"></div>
<div class="row">
<div class="hr-line-dashed"></div>
<div class="text-center col-md-12 padding-15">
<button class="btn btn-primary btn-lg" ng-click="saveGameSetting()" formnovalidate translate>
<i class='fa fa-circle-o-notch fa-spin' ng-if="showBusy"></i> Save
</button>
</div>
</div>
</form>
and the controller:
function GamePreferenceCtrl($scope, $filter, Tournament, Notification) {
$scope.$parent.child.game = $scope;
$scope.selectedItems = [];
$scope.formData = {};
$scope.loadConfig = function () {
Tournament.loadGameConfig($scope.id).then(function (response) {
$scope.gameConfigs = {};
if (response.data.tournamentPrefs) {
_.each(response.data.tournamentPrefs, function (val) {
if ($scope.formData.settings === undefined) {
$scope.formData.settings = [];
}
$scope.formData.settings.push({section: val.name, value: val.value});
});
$scope.formData = {};
};
$scope.dropdownItemClicked = function(name, value) {
if($scope.formData.settings === undefined) {
$scope.formData.settings = {};
$scope.formData.settings[name] = value;
} else {
console.log('inside else');
$scope.formData.settings[name] = value;
}
};
_.each(response.data.provisions, function (val) {
if ($scope.gameConfigs[val.section] === undefined) {
$scope.gameConfigs[val.section] = [];
}
$scope.gameConfigs[val.section].push({name: val.key, value: val.value});
});
});
};
I know this is a long read, and I truly appreciate anyone who might be able to help me out with this. Thank you!
You may do like
var o = {z:1, f:2, a:7}
p = Object.assign({},o),
q = Object.assign(o)
console.log(o, p, o === p);
console.log(o, q, o === q);

collect filtered ng-repeat data to use in controller

I am trying to build a picture library in a responsive grid with data coming down from a Mongo DB. Data from db is in this form. name, image, keyword, bio, reference", searchable,
I start with an ng-repeat to display category selection checkboxes based on the picture keywords. This seems to work fine.
p CATEGORIES:
span.formgroup(data-ng-repeat='category in mongoController.catData')
input.checks( type='checkbox', ng-model='mongoController.filter[category]')
| {{category}}
Here is the factory that sorts and identifies the keyword checkboxes:
getCategories: function (cat) {
return (cat || []).map(function (pic) {
return pic.keyword;
}).filter(function (pic, idx, array) {
return array.indexOf(pic) === idx;
});
}
From there I filter an ng-repeat to display pictures based on the checkbox selection, and/or a search field which works well also.
input.form-control.input-med(placeholder="Search" type='text', ng-model="search.searchable")
br
div.light.imageItem(data-ng-repeat="picture in filtered=( mongoController.allData | filter:search | filter:mongoController.filterByCategory)")
a(data-toggle="modal", data-target="#myModal", data-backdrop="static", ng-click='mongoController.selectPicture(picture)')
img( ng-src='../images/{{picture.image}}_tn.jpg', alt='Photo of {{picture.image}}')
p Number of results: {{filtered.length}}
Functions to display picture lists:
//Returns pictures of checkboxes || calls the noFilter function to show all
mongoController.filterByCategory = function (picture) {
return mongoController.filter[picture.keyword] || noFilter(mongoController.filter);
};
function noFilter(filterObj) {
for (var key in filterObj) {
if (filterObj[key]) {
return false;
}
}
return true;
}
If you click one of the pictures a modal box pops up with all of the input fields where you can edit image specific fields.
The part I am really struggling with is how to gather the usable data from the filtered ng-repeat to use in the controller so when the modal box is up I can scroll right or left through the other pictures that met the criteria of the search.
Any suggestions would help, even why the hell are you doing it this way?
When you declare the ng-repeat that filters the pictures, your filtered variable belongs to the current $scope (which I cannot infer from the question, as it stands). You could associate the filtered variable to a specific controller by using Controller As syntax: (i.e. using <elem ng-repeat="picture in ctrl.filtered = (ctrl.data | filter1 | filterN)"/>)
Example: (also in jsfiddle)
var mod = angular.module('myapp', []);
mod.controller('ctrl', function() {
var vm = this;
vm.data = ['alice', 'bob', 'carol', 'david', 'ema'];
vm.onbutton = function() {
// access here the filtered data that mets the criteria of the search.
console.log(vm.filtered);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp" ng-controller="ctrl as vm">
<input ng-model="search" type="text" />
<p data-ng-repeat="item in vm.filtered = (vm.data | filter:search)">
{{item}}
</p>
<p>Items: {{vm.filtered.length}}</p>
<button ng-click="vm.onbutton()">Show me in console</button>
</div>

How to filter through a table using ng-repeat checkboxes with Angularjs

Once upon a time this was working but somehow it's broken. I want to be able to produce checkboxes using ng-repeat to get as many checkboxes as required based on stored data and use these to filter through a table produced.
Additionally I don't want identical values for the checkboxes to be repeated.
I have made a plnkr with the code.
<div class="row">
<label data-ng-repeat="x in projects">
<input
type="checkbox"
data-ng-true-value="{{x.b}}"
data-ng-false-value=''
ng-model="quer[queryBy]" />
{{x.b}}
</label>
</div>
http://plnkr.co/edit/RBjSNweUskAtLUH3Ss6r?p=preview
So in summary.
Checkboxes to filter Ref.
Checkboxes to be unique.
Checkboxes to be made based off ng-repeat using Ref.
Okay, here's how to do it.
First, let's add a couple of lines of CSS in your to make sure all the checkboxes are visible:
<style>
.row { margin-left: 0px }
input[type=checkbox] { margin-left: 30px; }
</style>
Next, add the following lines to your controller:
app.filter('unique', function() {
return function (arr, field) {
var o = {}, i, l = arr.length, r = [];
for(i=0; i<l;i+=1) {
o[arr[i][field]] = arr[i];
}
for(i in o) {
r.push(o[i]);
}
return r;
};
})
app.controller("maincontroller",function($scope){
$scope.query = {};
$scope.quer = {};
$scope.queryBy = '$';
$scope.isCollapsed = true;
$scope.selectedRefs = [];
$scope.myFilter = function (item) {
var idx = $scope.selectedRefs.indexOf(item.b);
return idx != -1;
};
$scope.toggleSelection = function toggleSelection(id) {
var idx = $scope.selectedRefs.indexOf(id);
if (idx > -1) {
$scope.selectedRefs.splice(idx, 1);
}
else {
$scope.selectedRefs.push(id);
}
};
Phew.
For some reason, your Plunkr's version of AngularJS didn't recognise the unique attribute, so I added one to your controller.
Finally, change your html to this:
<div class="row">
<label data-ng-repeat="x in projects | unique:'b' | orderBy:'b'" >
<input
id="x.b"
type="checkbox"
ng-click="toggleSelection(x.b)"
ng-init="selectedRefs.push(x.b)"
ng-checked="selectedRefs.indexOf(x.b) > -1" />
{{x.b}}
</label>
</div>
... and your ng-repeat to this...
<tr ng-click="isCollapsed = !isCollapsed" ng-repeat-start="x in projects | filter:myFilter | orderBy:orderProp">
If you're interested in knowing how this works, add these lines:
<div style="margin:10px 10px 30px 10px">
<pre>{{ selectedRefs }} </pre>
</div>
I love this trick: you can see the exact contents of our "selectedRefs" array, and see it change as we tick/untick our checkboxes. This really helps when developing/testing our bindings!
As you can see, these changes use the new unique function to get your list of distinct values from your project array, and when the page first loads, we push all of the values into our new "selectedRefs" array.
["123","321","456","654","789","987"]
Then, as you tick/untick the checkboxes, we add/remove that item from this list.
Finally, we use that filter in the ng-repeat.
ng-repeat-start="x in projects | filter:myFilter | orderBy:orderProp"
Job done !
Update
If you wanted to start off with all checkboxes unticked, then it's a simple change. Just remove this line...
ng-init="selectedRefs.push(x.b)"
..and change the myFilter function to show all items initially..
$scope.myFilter = function (item) {
if ($scope.selectedRefs.length == 0)
return true;
var idx = $scope.selectedRefs.indexOf(item.b);
return idx != -1;
};
And to add a "Clear all" button, simply add a button to your form which calls a function in your AngularJS controller like this..
$scope.clearAll = function () {
$scope.selectedRefs = [];
};
(I haven't tested these suggestions though.)
ng-false-value directive needs a value set. Try ng-false-value='false' or ng-false-value='null' (in fact you can skip this one entirely if it has to just be a falsy value and not something concrete, like a string or certain number).
As you've pointed out in the comments, after selecting and then clearing the checkboxes, all rows are filtered out. It happens because unchecking the checkbox will set its value to false, and this does not agree with your entities' values (as you probably know, just stating it for others).
Therefore you do need to set this value to empty string in the end. That'd be the way:
$scope.$watch('quer.$', function () {
if ($scope.quer.$ === false) {
$scope.quer.$ = '';
}
});

Knockout: Best way to bind visibility to both item and a parent property?

I am creating an edit screen where I want people to delete items from a list. The list is displayed normally, until the "controller" object goes into edit mode. Then the user can delete items. Items should be flagged for deletion and displayed as such, then when the user saves the edit, they are deleted and the server notified.
I actually have this all working, but the only way I could do it was using literal conditions in the bindings, which looks ugly and I don't really like. Is there a better way of doing it?
Working Fiddle: http://jsfiddle.net/L1e7zwyv/
Markup:
<div id="test">
<a data-bind="visible: IsViewMode, click: edit">Edit</a>
<a data-bind="visible: IsEditMode, click: cancel">Cancel</a>
<hr/>
<ul data-bind="foreach: Items">
<li data-bind="css: CssClass">
<span data-bind="visible: $parent.IsViewMode() || $data._Deleting(), text: Value"></span>
<!-- ko if: $parent.IsEditMode() && !$data._Deleting() -->
<input type="text" data-bind="value: Value"/>
<a data-bind="click: $parent.deleteItem">Del</a>
<!-- /ko -->
</li>
</ul>
</div>
Code:
function ItemModel(val)
{
var _this = this;
this.Value = ko.observable(val);
this._Deleting = ko.observable();
this.CssClass = ko.computed(
function()
{
return _this._Deleting() ? 'deleting' : '';
}
);
}
function ManagerModel()
{
var _this = this;
this.Items = ko.observableArray([
new ItemModel('Hell'),
new ItemModel('Broke'),
new ItemModel('Luce')
]);
this.IsEditMode = ko.observable();
this.IsViewMode = ko.computed(function() { return !_this.IsEditMode(); });
this.edit = function(model, e)
{
this.IsEditMode(true);
};
this.cancel = function(model, e)
{
for(var i = 0; i < _this.Items().length; i++)
_this.Items()[i]._Deleting(false);
this.IsEditMode(false);
};
this.deleteItem = function(model, e)
{
model._Deleting(true);
};
}
ko.applyBindings(new ManagerModel(), document.getElementById('test'));
you could:
wrap another span around to separate the bindings but this would be less efficient.
use both a visible: and if: binding on the same element to achieve the same functionality,
write a function on the itemModel isVisible() accepting the parent as an argument making your binding visible: $data.isVisible($parent).
Afterthought: If this comes up in multiple places you could write a helper function to combine visibility bindings
// reprisent variables from models
var v1 = false;
var v2 = false;
var v3 = false;
// Helper functions defined in main script body - globally accessible
function VisibilityFromAny() {
var result = false;
for(var i = 0; i < arguments.length; i++ ) result |= arguments[i];
return Boolean(result);
}
function VisibilityFromAll() {
var result = true;
for(var i = 0; i < arguments.length; i++ ) result &= arguments[i];
return Boolean(result);
}
// represent bindings
alert(VisibilityFromAny(v1, v2, v3));
alert(VisibilityFromAll(v1, v2, v3));
The third option is the most popular technique with MVVM aficionados like yourself for combining variables in a single binding from what I've seen, it makes sense and keeps all the logic away from the view markup in the view models.
Personally I like the syntax you have at present, (even though I count myself amongst the MVVM aficionado gang as well) this clearly shows in the view markup that the visibility of that element is bound to 2 items rather then hiding these details in a function.
I try to think of view models as a model for my view, not just a place where logic resides. When possible I also try to move complex logic back the view model and use descriptive names for my variables so the code is more readable.
I would suggest adding this to your view model -
var isViewable = ko.computed(function () { return IsViewMode() || _Deleting(); });
var isEditable = ko.computed(function() { return IsEditMode() && !_Deleting(); });
And in your view -
<li data-bind="css: CssClass">
<span data-bind="visible: isViewable, text: Value"></span>
<!-- ko if: isEditable -->
<input type="text" data-bind="value: Value"/>
<a data-bind="click: $parent.deleteItem">Del</a>
<!-- /ko -->
</li>
This cleans the bindings up and allows you to more easily adjust the logic without having to do many sanity checks in your view and view model both. Also I personally name variables that return a boolean such as this as isWhatever to help be more descriptive.
The benefit is that as your view and view model grow larger you can keep the DOM clean of clutter and also your view model becomes testable.
Here is a 'code complete' version of your fiddle with this added -
http://jsfiddle.net/L1e7zwyv/3/

Categories

Resources