When a todo is created,I push it onto this array:
$scope.$on('todo:created',function(event,todo){
$scope.model.todos.push(todo);
});
I am trying to delete a todo using the $scope.$on functionality like this:
//from a child scope for the item
$scope.actions.remove = function remove()
{
TodoService.delete($scope.model.todo);
$scope.$emit('todo:deleted',$scope.model.todo);
};
//from a parent of the scope which has ng-repeat:
$scope.$on('todo:deleted',function(event,todo){
for(var i =0;i<$scope.model.todos.length;i++)
{
if(todo._id === $scope.model.todos[i]._id)
{
console.log(i);
$scope.model.todos.splice(i,1);
break;
}
}
});
I find that this seemingly normal code causes a lot of issues:
1)last item gets deleted correctly
2)when deleting the penultimate item,the last item gets deleted
3)all other items are unresponsive on delete
I find that the UI renders with the correct items deleted on page reload.
I have tried changing the code to:
$scope.$on('todo:deleted',function(event,todo){
$scope.model.todos = $.grep($scope.model.todos, function (todoItem, i) {
if (todoItem._id === todo._id) {
console.log(i);
return false;
} else {
return true;
}
});
});
The entire code can be found here on github
EDIT:
My code uses ng-repeat like this:
<section class="ui three column doubling page grid">
<div class="column" ng-repeat="todo in model.todos track by $index">
<todo-item value="todo"></todo-item>
</div>
</section>
UPDATE:
I have tried to accomplish the delete using this code:
var onItemDeleted = function onItemDeleted(todo){
var todos = $scope.model.todos;
var checkIndex = function checkIndex(t){
return t._id !== todo._id;
};
todos = todos.filter(checkIndex);
//$scope.model.todos = todos;
};
$scope.$on('todo:deleted',function(event,todo){
$scope.$apply(onItemDeleted(todo));
});
var onTodoAdded = function onTodoAdded(todo){
var todos = $scope.model.todos;
todos.push(todo);
};
$scope.$on('todo:created',function(event,todo){
$scope.$apply(onTodoAdded(todo));
});
In both these cases,I get the error:
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.3.15/$rootScope/inprog?p0=%24digest
at REGEX_STRING_REGEXP (angular.js:66)
at beginPhase (angular.js:14823)
at Scope.$get.Scope.$apply (angular.js:14567)
at app.js:218
at Scope.$get.Scope.$emit (angular.js:14715)
at app.js:264
at processQueue (angular.js:13251)
at angular.js:13267
at Scope.$get.Scope.$eval (angular.js:14469)
at Scope.$get.Scope.$digest (angular.js:14285)
I have tried replacing the data by replacing the data from the service like this:
$scope.$on('todo:deleted',function(event,todo){
var todosPromise = TodoService.get();
todosPromise.then(function(data){
$scope.model.todos = data;
});
});
And I have changed the event code to fire only after the delete operation is completed:
$scope.actions.remove = function remove(id)
{
TodoService.deleteItem($scope.model.todo)
.then(function(){
$scope.$emit('todo:deleted',$scope.model.todo);
})
.catch(function(err){
console.log(err,err.stack);
});
};
I find that the data from the service or upon splicing is obtained correctly but the wrong element is pulled by ng-repeat,is it because I am using track by $index.
I faced some issues with splice and used $grep to fix those issues.
Can you try the below code:
$scope.$on('todo:deleted',function(event, todo) {
$scope.model.todos = $.grep($scope.model.todos, function (todoItem, i) {
if (todoItem._id === todo._id) {
return false;
console.log(i);
} else {
return true;
}
});
});
Related
In the following code in angular.js 1.x, I need to create a dashboard dataset which makes a call every few second and update the dom. However it is flickering because of I think $scope.DOMdataArray = []
I have already tried track by $index but didn't help. Is there any way to resolve this issue or write a code in such a way that DOM generated by ng-repeat doesn't flicker?
function someDataInterval () {
$scope.DOMdataArray = []
_.map(urlsArray,function(url) {
$http.get(url).then(function(res) {
// some work on response
$scope.DOMdataArray.push(/*some data for ng-repeat*/)
})
})
}
someDataInterval ()
setInterval(someDataInterval,5000)
Have you tried this ?
function someDataInterval () {
_.map(urlsArray,function(url) {
$http.get(url).then(function(res) {
// some work on response
$scope.DOMdataArray = [];
$scope.DOMdataArray.push(/*some data for ng-repeat*/)
})
})
}
someDataInterval ()
setInterval(someDataInterval,5000)
If this doesn't work you can always use a small loading icon so that flickering is not visible. use ng-show for showing list.
function someDataInterval () {
$scope.loading = true;
_.map(urlsArray,function(url) {
$http.get(url).then(function(res) {
// some work on response
$scope.DOMdataArray = [];
$scope.DOMdataArray.push(/*some data for ng-repeat*/)
$scope.loading = false;
})
})
}
someDataInterval ()
setInterval(someDataInterval,5000)
and HTML :
<h1 ng-show="!loading" ng-repeat="x in DOMdataArray">{{x.name}}</h1>
<img ng-show="loading" src="img/image-loading.gif" />
I've created a list of toggles to select bluetooth devices and connect it, and I've created a controller.js and service.js for them. However, the ng-changeonly triggers when ion-toggles first loaded in view, and were not triggering for changes afterwards.
Here is my snippet of setting.html:
<ion-toggle ng-repeat="item in devicesList"
ng-model="item.checked"
ng-change="connectDevice(devicesList)">
{{ item.address }}
</ion-toggle>
and here is my settingController.js:
(function () {
'use strict';
angular.module('i18n.setting').controller('SettingController', SettingController);
SettingController.$inject = ['$scope','SettingService'];
function SettingController($scope, SettingService) {
$scope.devicesList = [];
$scope.scanDevices = SettingService.scanDevices($scope.devicesList);
$scope.connectDevice = SettingService.connectDevice($scope.devicesList);
};
})();
and here is my settingService.js:
(function(){
'use strict';
angular.module('i18n.setting').service('SettingService', SettingService);
SettingService.$inject = [];
function SettingService(){
var self = this;
this.scanDevices = function(devicesCollection) {
window.bluetoothSerial.discoverUnpaired(function(devices) {
console.log("in discoverUnpaired callback, setting page");
devices.forEach(function(device) {
console.log(device.name);
device.checked = false;
devicesCollection.push(device);
});
}, function(error) {
console.log("in discoverUnpaired error function");
console.log(error);
});
};
this.connectDevice = function(devicesCollection) {
console.log(devicesCollection);
console.log("In connectDevice");
for (var i =0; i<devicesCollection.length; i++)
{
console.log(devicesCollection[i].id + " " + devicesCollection[i].checked);
if (devicesCollection[i].checked == true)
{
console.log(devicesCollection[i].name);
window.bluetoothSerial.connect(devicesCollection[i].address,
function(data) {console.log("This device is"+macaddress); console.log(data);},
function(error) {console.log(error);});
}
}
};
}
})();
And if I define this function directly in settingController.js, it's working properly and can detect every change of the toggle. Also, I noticed that my scanDevices function will be triggered even if I don't click the button. I don't know why is it. Will there be someone tell me what cause is it? Any help, thanks
The controller should put the service functions on the scope; not invocations of the service functions.
(function () {
'use strict';
angular.module('i18n.setting').controller('SettingController', SettingController);
SettingController.$inject = ['$scope','SettingService'];
function SettingController($scope, SettingService) {
$scope.devicesList = [];
//$scope.scanDevices = SettingService.scanDevices($scope.devicesList);
$scope.scanDevices = SettingService.scanDevices;
//$scope.connectDevice = SettingService.connectDevice($scope.devicesList);
$scope.connectDevice = SettingService.connectDevice;
};
})();
Since the scanDevices function has no return statement, scanDevices($scope.deviceList) returns undefined. Thus $scope.scanDevices was getting set to undefined.
Update
Can you also explain why scanDevices get invoked before I press the Scan button?
Which was bind to:
<button class="button button-stable"
ng-click="scanDevices(devicesList)">Scan Devices
</button>
By binding an invocation of the function to the scope variable instead of the function itself:
//ERRONEOUS
$scope.scanDevices = SettingService.scanDevices($scope.devicesList);
//CORRECT
$scope.scanDevices = SettingService.scanDevices;
The expression SettingService.scanDevices($scope.devicesList) invokes the function before the button is clicked. And assigns undefined to the $scope.scanDevices variable.
Edit:
This is precisely what I want to do:
Template.FrameItems.helpers({
frames: function() {
var trialId = Session.get('trialId');
return Frames.find({trialId: trialId});
// when new frames are rendered, I want to call positionElements()
}
});
Template.FrameItems.onRendered(function() {
this.autorun(function() {
var trialId = Session.get("trialId");
positionElements();
// problem: positionElements() is called before the DOM is updated from `frames` helper function
})
});
EDIT2:
This is my second attempt which doesn't work.
var frameDep = new Tracker.Dependency;
Template.FrameItems.helpers({
frames: function() {
var trialId = Session.get('trialId');
frameDep.changed();
return Frames.find({trialId: trialId});
// when new frames are rendered, I want to call positionElements()
}
});
Template.FrameItems.onRendered(function() {
this.autorun(function() {
frameDep.depend();
positionElements();
});
The same problem still remains. By the time positionElements() is invoked, the DOM is still not updated with the new frames objects. I need a way to find out when the DOM is updated. onRendered() is not called after the very first time the template is rendered, which is problematic in my case.
EDIT3:
I ended up doing this, but I feel like there should be a better solution.
if (Meteor.isClient) {
var frameItemsTemplate;
Template.TrialWorkSpace.onRendered(function() {
this.autorun(function() {
var trialId = Session.get("trialId");
if (frameItemsTemplate) {
Blaze.remove(frameItemsTemplate);
}
frameItemsTemplate = Blaze.render(Template.FrameItems,
$('.frame-items-container')[0]);
});
});
Template.FrameItems.helpers({
frames: function() {
var trialId = Session.get('trialId');
return Frames.find({trialId: trialId});
}
});
Template.FrameItems.onRendered(function() {
positionElements();
});
}
And the template file
<template name="TrialWorkSpace">
<div class="trial-workspace-container">
<div class="row frame-items-container">
<!-- populated programmatically instead of {{> FrameItems}} -->
</div>
</div>
</template>
<template name="FrameItems">
{{#each frames}}
<div id="frame-{{_id}}" class="frame-preview-item cyan lighten-5">
<div class='frame-name'>{{name}}</div>
<div class="ep"></div>
</div>
{{/each}}
</template>
Your first assumption is wrong. onRendered only renders when the template is inserted into the DOM, if you want reactivity, you'll wanna stick an autorun in the callback.
if (Meteor.isClient) {
Template.TrialWorkSpace.onCreated({
dep = new Tracker.Dependency();
});
Template.FrameItems.helpers({
frames: function() {
var trialId = Session.get('trialId');
console.log("I am getting called");
dep.changed();
return Frames.find({trialId: trialId});
}
});
Template.TrialWorkSpace.onRendered(function() {
Tracker.autorun(function() {
dep.depend();
console.log("onRendered");
})
})
}
I have solved following way:
Template.TrialWorkSpace.onRendered(function() {
var self = this;
self.autorun(function(){
var trialId = Session.get("trialId");
Tracker.afterflush(function(){
positionElements();
}.bind(self));
})
});
when Session.get("trialId") is set or change than it will call positionElements() after dom refresh.
Below in my list, one of the divs at the bottom has a removePortfolio function. This function's job is to activate the ng-hide="tickerRemoved" but only for that 1 list item, not all the list items.
HTML Gist: https://gist.github.com/leongaban/cf72e5d0229155dd011f
Directive Gist: https://gist.github.com/leongaban/22a8feb9dbeea0b90135
<ul ng-show="loadingTickersDone" class="tickers-list">
<li class="ticker-li"
ng-repeat="ticker in tickers"
ng-hide="tickerRemoved"
ng-class="{'selected':toggleTicker.item == $index}"
ng-mouseleave="hideTickerOptions()">
<div class="ticker"
ng-click="toggleTicker.item = $index;
selectTicker(ticker.ticker);
revealTickerOptions()">
{{ticker.ticker}}
</div>
<div class="add-to-portfolio"
ng-show="tickerOptions"
ng-mouseleave="hideTickerOptions()">
<div ng-show="addOption"
ng-click="addPortfolio(ticker.ticker)">+ Portfolio</div>
<div ng-show="removeOption"
ng-click="removePortfolio(ticker.ticker)">- Portfolio</div>
</div>
</li>
</ul>
Here is the remove function in the directive:
var vs = $scope;
vs.removePortfolio = function(ticker) {
this.tickerOptions = false;
ApiFactory.deleteWatchList(ticker).then(function(data) {
showMessage(ticker+' removed from portfolio!', data.data.status);
this.tickerRemoved = true;
});
};
I get an error with this.tickerRemoved = true; I think this is because the scope is lower in the chain?
For example, I'm using this in this function and it works fine because the function is higher in the markup/scope:
vs.revealTickerOptions = function() {
this.tickerOptions = true;
if (tickerView === 'all') {
this.addOption = true;
this.removeOption = false;
}
else if (tickerView === 'port') {
this.addOption = false;
this.removeOption = true;
}
};
How would I remove just the 1 <li class="ticker-li" item when clicking the removePortfolio() function?
ng-hide="tickerRemoved" should be ng-hide="ticker.tickerRemoved" since tickerRemoved is a property of a specific ticker.
Same with ng-show="tickerOptions"... should be ng-show="ticker.tickerOptions" from the looks of it.
ng-click="removePortfolio(ticker.ticker)"> should be ng-click="removePortfolio(ticker)"> since you probably want to pass the entire ticker object.
After that, you will need to update your remove ticker function, something like this should work:
vs.removePortfolio = function(tickerObject) {
var ticker = tickerObject.ticker
tickerObject.tickerOptions = false;
ApiFactory.deleteWatchList(ticker).then(function(data) {
showMessage(ticker+' removed from portfolio!', data.data.status);
tickerObject.tickerRemoved = true;
});
};
As a general observation, it looks like you are leaning on this too much. this can be a very confusing keyword and should only be used (in my opinion) when there is both a good reason to do so and doing so will not cause confusion during later code maintenance.
Hi guys I have tried this approach to get information from an array of Meteor.users but it's not working and I don't have any idea to solve this problem. Thanks in advance!
Publish users
Deps.autorun(function (handle) {
Meteor.publish('directory', function () {
if (this.userId) {
var user = Meteor.users.findOne(this.userId);
var x = user.profile.x;
return Meteor.users.find({"profile.x": x});
} else {
handle.stop();
}
});
});
Meteor.users.allow({
remove:function() {
var user = Meteor.user();
var x = user.profile.x;
if(x === "admin") {
return true;
} else {
return false;
}
},
});
Helper:
listTeam: function () {
var userTeam = Meteor.users.find({}).fetch();
return _.map(userTeam || [], function(user) { return user.emails[0].address || user._id;});
},
Template:
<ul>
{{#each listTeam}}
<div>
<li id="item">{{this}}</li><button id="deleteTeam" class="button">Delete</button>
</div>
{{/each}}
</ul>
First approach:
'click #deleteTeam': function (event, template) {
var id = template.data._id;
Meteor.users.remove({_id:id});
}
Returns undefined (only in the first item of iteration)
I've tried with jQuery but it's not useful since I just get the email address and the first item only.(I'm not iterating id's and I think this seems not to be a good solution).
Inside template event functions you can use this to get the instance data.
Example:
'click #deleteTeam': function (event, template) {
var id = this._id;
Meteor.users.remove({_id:id});
}
or alternatively shorter:
'click #deleteTeam': function () {
Meteor.users.remove({_id: this._id});
}
if you run into further issues, it might be a permissions problem, I recommend using this pattern:
Meteor.users.allow({
remove: function(userId, doc) {
var user = Meteor.users.findOne({_id: userId});
return user ? user.profile.admin : false;
}
});