I have to delete items in a for loop the moment I close a bootstrap modal.
Currently I am setting a delay of 3 secs on the modal close, so that the delete can happen within those 3secs in the background but this is not very efficient.
What is the best way to ensure that the modal closes only when all the items are deleted successfully? Perhaps by making the delete synchronous ? Or promises?
$scope.idList = [1, 2, 3];
$scope.deleteItems = function(deleteList){
angular.forEach( deleteList, function(item) {
DeleteAPI.remove({itemId: item}, {}, $scope.delSuccess, $scope.delError);
});
}
$scope.close = function(){
var pmodal = $modal.open( {
templateUrl: 'route/pmodal.html',
controller: 'DeleteCtrl'
} );
pmodal.result.then(
function(check) {},
function(check) {
if ( check=='proceed' ) {
$scope.deleteItems( $scope.idList );
$timeout( function() {
$modalInstance.dismiss('cancel');
}, 3000);
}
},
function(check) {}
);
}
You can use $q.all or you can simple count responses as Javascript is not multithread:
pmodal.result.then(
var success = 0;
var failed = 0;
var check = function() {
if (success + failed == deleteList.length) {
if (failed != 0) {
// do smth?
} else {
$modalInstance.dismiss('cancel');
}
}
}
angular.forEach( deleteList, function(item) {
DeleteAPI.remove({itemId: item}, {}, function() {
success++;
check();
}, function() {
failed++;
check();
});
});
Related
I'm doing a dashboard and I am having a problem.
When I resize my window it scrolls by itself.
Everything is responsive but I do not understand why it scrolls.
Thanks you !
ps: I upload my site if you want check :)
https://edtmimi.000webhostapp.com/dashBoard/
Before resize
After resize
What I want
Update your profileScroll.js file to the code below.
When you resize your browser, the scroll position changes. Since you use this to animate and calculate the position for your pages, you need to recalculate them when resizing the window.
window.addEventListener('load', function () {
var delayInMilliseconds = 1;
function updateScrollPosition() {
if (window.scrollY != document.getElementById("homePage").offsetTop || window.scrollX != document.getElementById("homePage").offsetLeft) {
window.scroll(document.getElementById("homePage").offsetLeft, document.getElementById("homePage").offsetTop);
} else {
document.documentElement.style.animationFillMode = "forwards";
document.documentElement.style.animationDelay = "1s";
}
document.documentElement.style.scrollBehavior = "smooth";
}
setTimeout(function () {
updateScrollPosition();
}, delayInMilliseconds);
document.getElementById("profileButton").addEventListener("click", function () {
window.scrollTo(document.getElementById("profilePage").offsetLeft, document.getElementById("profilePage").offsetTop);
});
for (i = 0; i < document.getElementsByClassName("returnToHomePage").length; i++) {
document.getElementsByClassName("returnToHomePage")[i].addEventListener("click", function () {
window.scrollTo(document.getElementById("homePage").offsetLeft, document.getElementById("homePage").offsetTop);
});
}
document.getElementById("mailButton").addEventListener("click", function () {
window.scrollTo(document.getElementById("mailPage").offsetLeft, document.getElementById("mailPage").offsetTop);
});
document.getElementById("noteButton").addEventListener("click", function () {
window.scrollTo(document.getElementById("notePage").offsetLeft, document.getElementById("notePage").offsetTop);
});
document.getElementById("gameButton").addEventListener("click", function () {
window.scrollTo(document.getElementById("gamePage").offsetLeft, document.getElementById("gamePage").offsetTop);
});
document.getElementById("calendarButton").addEventListener("click", function () {
window.scrollTo(document.getElementById("calendarPage").offsetLeft, document.getElementById("calendarPage").offsetTop);
});
document.getElementById("voiceButton").addEventListener("click", function () {
window.scrollTo(document.getElementById("voicePage").offsetLeft, document.getElementById("voicePage").offsetTop);
});
document.getElementById("buyButton").addEventListener("click", function () {
window.scrollTo(document.getElementById("buyPage").offsetLeft, document.getElementById("buyPage").offsetTop);
});
document.getElementById("paramsButton").addEventListener("click", function () {
window.scrollTo(document.getElementById("paramsPage").offsetLeft, document.getElementById("paramsPage").offsetTop);
});
window.addEventListener('resize', function(){
updateScrollPosition();
});
});
But I would make it a bit more generic:
window.addEventListener('load', function() {
const delayInMilliseconds = 1;
let currentPageId = 'homePage';
function scrollToPage(pageId) {
currentPageId = pageId;
window.scrollTo(document.getElementById(pageId).offsetLeft, document.getElementById(pageId).offsetTop);
}
setTimeout(function() {
document.documentElement.style.animationFillMode = 'forwards';
document.documentElement.style.animationDelay = '1s';
document.documentElement.style.scrollBehavior = 'smooth';
scrollToPage(currentPageId);
}, delayInMilliseconds);
let actions = [
{ buttonId: 'profileButton', pageId: 'profilePage' },
{ buttonId: 'mailButton', pageId: 'mailPage' },
{ buttonId: 'noteButton', pageId: 'noteButton' },
{ buttonId: 'gameButton', pageId: 'gamePage' },
{ buttonId: 'calendarButton', pageId: 'calendarPage' },
{ buttonId: 'voiceButton', pageId: 'voicePage' },
{ buttonId: 'buyButton', pageId: 'buyPage' },
{ buttonId: 'paramsButton', pageId: 'paramsPage' },
];
// Make sure you use `let` instead of `var`. The scope of `var` is global.
for (let i = 0; i < actions.length; i++) {
document.getElementById(actions[i].buttonId).addEventListener('click', function() {
scrollToPage(actions[i]);
});
}
// Check all document clicks, if the target has the class 'returnToHomePage' go back to home page.
// This way you don't have to loop over the buttons
document.addEventListener('click', function(event) {
if (event.target.classList.contains('returnToHomePage')) {
scrollToPage('homePage');
}
});
window.addEventListener('resize', function() {
scrollToPage(currentPageId);
});
});
I'm having the search column with checkbox along with folder names. when I click the checkbox of corresponding folder, it will show their items. As well as when I click on multiple checkbox it will show their corresponding items. But when I uncheck the folder, the corresponding items doesn't remove. So I need the hard reload or refresh when I check or uncheck the checkbox or need to clear the cache for every check or uncheck.
Here is my Code:
Checkbox: Core.component.Checkbox.extend({
click: function () {
var ret=null;
var nav = this.get("controller").get('selectedNavigator');
ret = this._super.apply(this, arguments);
var states=null;
Ember.run.schedule('sync', this, function () {
Ember.run.schedule('render', this, function () {
states = this.get('parentView.itemStates');
var values = Object.keys(states).filter(function (key) {
if(states[key]){
return states[key];}
else{return;}
});
if (values.length === 0) {
Core.Action('core:contentHome', {});
} else {
this.set('parentView.model.values',values);
nav.publish();
}
});
});
return ret;
}
}),
For the Publish:
publish: function () {
var currentResultSet = this.get('resultSet'),
ctl = this.get('controller'),
form = ctl.get('formModel'),
resultSet,
data = form.sleep(2000);
if (Object.keys(data).length === 0) {
Core.view.Menu.create({
anchor: $('*[data-class-name="Core.Tab.Content.Controller.NavigationRefresh"]'),
model: [
Core.model.Menu.Item.create({
label: "Please select something to search.",
icon: 'dialog_warning'
})
]
}).show();
return;
}
resultSet = form.send();
ctl.set('loadState', 'loading');
ctl.set('resultSet', Core.model.BlankResultSet.create({
loadState: 'loading',
tabContext: Ember.get(form, 'resultSet.tabContext')
}));
resultSet.fail(function (err) {
ctl.set('loadState', 'loaded');
console.log(currentResultSet);
ctl.set('resultSet', currentResultSet);
}).always(function () {
ctl.set('loadState', 'loaded');
});
},
I'm trying to add a function in my JS for a basic ToDo app that I'm working on using Angular Material and I need to know how I can get it to read the value/property of an md-checkbox (whether or not it is ticked).
The reason for this is I'm trying to make an alert appear informing the user that they need to select at least one checkbox if none are currently selected and they click on the Delete button at the bottom.
Anyone know how I could do this?
Codepen: http://codepen.io/anon/pen/QpdpEa.
JS:
var app = angular.module('todoApp', ['ngMaterial']);
function menuController ($scope, $mdDialog) {
var originatorEv;
this.openMenu = function($mdOpenMenu, ev) {
originatorEv = ev;
$mdOpenMenu(ev);
};
};
app.controller('todoController', function($scope, $mdDialog, $mdToast) {
$scope.sortBy = '-addedOn';
$scope.taskList = [
{ name: 'Task 1', completed: false, addedOn: 1488722128000 },
{ name: 'Task 2', completed: false, addedOn: 1488722128000 },
];
$scope.addTask = function() {
if (angular.isUndefined($scope.taskName) || $scope.taskName.length === 0) {
var alert = $mdDialog.alert()
.parent(angular.element(document.querySelector('#popupContainer')))
.clickOutsideToClose(true)
.title('Error')
.textContent('You must enter a task name')
.ok('Close');
$mdDialog.show( alert )
.finally(function() {
alert = undefined;
});
}
else {
$scope.taskList.push({name: $scope.taskName, addedOn: Date.now()});
$scope.taskName = "";
var pinTo = $scope.getToastPosition();
$mdToast.show (
$mdToast.simple()
.textContent('Task Added')
.position(pinTo)
.hideDelay(3000)
)
}
};
$scope.selectAll = function() {
angular.forEach($scope.taskList, function(task) {
task.completed = true;
});
};
$scope.selectNone = function() {
angular.forEach($scope.taskList, function(task) {
task.completed = false;
});
};
$scope.delete = function(ev) {
var confirm = $mdDialog.confirm()
.title ('Are you sure you want to delete the selected tasks?')
.textContent ('Deleted tasks can\'t be recovered.')
.targetEvent (ev)
.ok ('Confirm')
.cancel ('Cancel')
clickOutsideToClose: false;
$mdDialog.show(confirm).then(function() {
var pinTo = $scope.getToastPosition();
$mdToast.show (
$mdToast.simple()
.textContent('Tasks Deleted')
.position(pinTo)
.hideDelay(3000)
)
$scope.status = 'Tasks Deleted';
var i = $scope.taskList.length;
while (i--) {
var task = $scope.taskList[i];
if(task.completed) {
$scope.taskList.splice(i, 1);
}
}
},
function() {
$scope.status = 'Deletion Cancelled';
});
};
function DialogController($scope, $mdDialog) {
$scope.hide = function() {
$mdDialog.hide();
};
$scope.cancel = function() {
$mdDialog.cancel();
};
$scope.answer = function(answer) {
$mdDialog.hide(answer);
};
};
var last = {
bottom: false,
top: true,
left: false,
right: true
};
$scope.toastPosition = angular.extend({},last);
$scope.getToastPosition = function() {
sanitizePosition();
return Object.keys($scope.toastPosition)
.filter(function(pos) { return $scope.toastPosition[pos]; })
.join(' ');
};
function sanitizePosition() {
var current = $scope.toastPosition;
if ( current.bottom && last.top ) current.top = false;
if ( current.top && last.bottom ) current.bottom = false;
if ( current.right && last.left ) current.left = false;
if ( current.left && last.right ) current.right = false;
last = angular.extend({},current);
};
});
app.controller('toastController', function($scope, $mdToast) {
$scope.closeToast = function() {
$mdToast.hide();
}
});
HTML:
<md-card-actions layout="row" class="md-padding">
<md-button ng-click="selectAll()" class="md-raised md-primary">Select All</md-button>
<md-button ng-click="selectNone()" class="md-raised md-primary">Select None</md-button>
<md-button ng-click="delete()" class="md-raised md-warn md-hue-2">Delete</md-button>
</md-card-actions>
You can just iterate over the taskList variable and check if at least one element has property completed with true value.
I've added a custom function, binded to the Show button. If you click on it, you will see in the console true if there's at least one checkbox checked or false if none of the checkboxes is checked.
$scope.show = function(){
console.log($scope.taskList.some(v => v.completed))
}
http://codepen.io/anon/pen/BWpWmw?editors=1010
I've made changes to your code that you can see & run here: http://codepen.io/anon/pen/jBymPd?editors=1010
I'm using the $filter service to get the task(s) that have completed member set to true.
If nothing has been checked, then you can show an alert to at-least select one task to delete. If one or more task is checked, then you just delete them.
The new changes in your code are shown below:
app.controller('todoController', function($scope, $mdDialog, $mdToast, $filter) {
$scope.delete = function(ev) {
var completedTasks = $filter('filter')($scope.taskList, { completed: true}, true);
console.log(completedTasks); // array of completed tasks, can be empty.
if (completedTasks.length > 0) {
console.log('show dialog box to confirm');
// your existing code.
var confirm = $mdDialog.confirm()
.title ('Are you sure you want to delete the selected tasks?')
.textContent ('Deleted tasks can\'t be recovered.')
.targetEvent (ev)
.ok ('Confirm')
.cancel ('Cancel')
clickOutsideToClose: false;
$mdDialog.show(confirm).then(function() {
var pinTo = $scope.getToastPosition();
$mdToast.show (
$mdToast.simple()
.textContent('Tasks Deleted')
.position(pinTo)
.hideDelay(3000)
)
$scope.status = 'Tasks Deleted';
var i = $scope.taskList.length;
while (i--) {
var task = $scope.taskList[i];
if(task.completed) {
$scope.taskList.splice(i, 1);
}
}
},
function() {
$scope.status = 'Deletion Cancelled';
});
} else {
alert('please select at-least one task to delete');
console.log('show alert to check at-least one task');
}
};
});
Hello again everyone.
EDIT: I want to emphasize that I can find no docs on the solution for this.
I am using a route to perform a search query to my server. The server does all the data logic and such and returns a list of objects that match the keywords given. I am taking those results and feeding them to the model so that I can use the {{#each}} helper to iterate over each result.
The problem I am having is that the model does not want to refresh when the searchText (search input) changes. I've tried several things. I'm not worried about creating too many ajax requests as my server performs the search query in 2ms. Here's what I have now.
App.SearchView = Ember.View.extend({...
EDIT:
Thank you for the answer.
App.SearchView = Ember.View.extend({
didInsertElement: function () {
this._super();
Ember.run.scheduleOnce('afterRender', this, this.focusSearch);
},
focusSearch: function () {
$(".searchInput").focus().val(this.get("controller").get('searchTextI'));
}
});
App.SearchRoute = Ember.Route.extend({
model: function () {
return this.controllerFor('search').processSearch();
}
});
App.SearchController = Ember.ArrayController.extend({
searchTextI: null,
timeoutid: null,
processid: null,
updateSearch: function () {
if(this.get('timeoutid')) {clearTimeout(this.get('timeoutid')); }
var i = this.get('searchTextI');
var sc = this;
clearTimeout(this.get('processid'));
this.controllerFor('index').set('searchText', i); //set the search text on transition
if(i.length < 3) {
this.set('timeoutid', setTimeout(function () {
sc.controllerFor('index').set("transitioningFromSearch", true);
sc.transitionToRoute('index');
}, 1500));
} else {
var self = this;
this.set('processid', setTimeout(function() {
self.processSearch().then(function(result) {
self.set('content', result);
});
}, 1000));
}
}.observes('searchTextI'),
processSearch: function () {
return $.getJSON('http://api.*********/search', { 'token': guestToken, 'search_query': this.get('searchTextI') }).then(function(data) { if(data == "No Results Found.") { return []; } else { return data; } }).fail(function() { return ["ERROR."]; });
}
});
Don't observe anything within a route and don't define any computed properties. Routes are not the place for these. Apart from that, the model doesn't fire because controller is undefined.
One way to achieve what you want:
App.SearchRoute = Ember.Route.extend({
model: function () {
this.controllerFor('search').searchQuery();
}.observes('controller.searchText') //not triggering an ajax request...
});
App.SearchController = Ember.ArrayController.extend({
searchQuery: function() {
return $.getJSON('http://api.**************/search', { 'token': guestToken, 'search_query': t }).fail(function() {
return null; //prevent error substate.
});
}
onSearchTextChange: function() {
var controller = this;
this.searchQuery().then(function(result) {
controller.set('content', result);
});
}.observes('searchText')
});
Putting an observes on the model hook is not going to do anything. You should simply do what you were thinking of doing and say
processSearch: function () {
this.set('content', $.getJSON....);
}
I've a controller that uses a Dialog from angular-ui/bootstrap:
function ClientFeatureController($dialog, $scope, ClientFeature, Country, FeatureService) {
//Get list of client features for selected client (that is set in ClientController)
$scope.clientFeatures = ClientFeature.query({clientId: $scope.selected.client.id}, function () {
console.log('getting clientfeatures for clientid: ' + $scope.selected.client.id);
console.log($scope.clientFeatures);
});
//Selected ClientFeature
$scope.selectedClientFeature = {};
/**
* Edit selected clientFeature.
* #param clientFeature
*/
$scope.editClientFeature = function (clientFeature) {
//set selectedClientFeature for data binding
$scope.selectedClientFeature = clientFeature;
var dialogOpts = {
templateUrl: 'partials/clients/dialogs/clientfeature-edit.html',
controller: 'EditClientFeatureController',
resolve: {selectedClientFeature: function () {
return clientFeature;
} }
};
//open dialog box
$dialog.dialog(dialogOpts).open().then(function (result) {
if (result) {
$scope.selectedClientFeature = result;
$scope.selectedClientFeature.$save({clientId: $scope.selectedClientFeature.client.id}, function (data, headers) {
console.log('saved.');
}, null);
}
});
};
});
I'm almost completely new to testing, and figured that maybe I need to test two things:
That a dialog opens when $scope.editClientFeature() is called
That $save is called successfully after a dialog is closed and returns a 'result'
My really messed up test now looks like this:
describe('ClientFeatureController', function () {
var scope, $dialog, provider;
beforeEach(function () {
inject(function ($controller, $httpBackend, $rootScope, _$dialog_) {
scope = $rootScope;
$dialog = _$dialog_;
//mock client
scope.selected = {};
scope.selected.client = {
id: 23805
};
$httpBackend.whenGET('http://localhost:3001/client/' + scope.selected.client.id + '/clientfeatures').respond(mockClientFeatures);
$controller('ClientFeatureController', {$scope: scope});
$httpBackend.flush();
});
});
it('should inject dialog service from angular-ui-bootstrap module', function () {
expect($dialog).toBeDefined();
console.log($dialog); //{}
});
var dialog;
var createDialog = function (opts) {
dialog = $dialog.dialog(opts);
};
describe('when editing a clientfeature', function () {
createDialog({});
console.log(dialog); //undefined
// var res;
// var d;
// beforeEach(function () {
// var dialogOpts = {
// template: '<div>dummy template</div>'
// };
// console.log(dialog);
// d = $dialog.dialog(dialogOpts);
// d.open();
// });
//
// it('should open a dialog when editing a client feature', function () {
// expect(d.isOpen()).toBe(true);
// });
});
});
The immediate problem now is that I'm unable to create/open a dialog. I get the following error:
Chrome 25.0 (Mac) ClientFeatureController when editing a clientfeature encountered a declaration exception FAILED
TypeError: Cannot call method 'dialog' of undefined
It would be great if someone has already written a test for a similar use case and can provide me with an example as I'm pretty lost.
Thanks,
Shaun
I was struggling with the same problem until right now, after trolling the the github repo i found the dialog tests and used that as a starting point :
var $dialog,$scope,$httpBackend;
beforeEach(module('ui.bootstrap.dialog'));
beforeEach(function(){
inject(function (_$dialog_, _$httpBackend_, $controller){
$dialog = _$dialog_;
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('/appServer/list')
.respond([{
id:1,
name:'test1'
},
{
id:2,
name:'test2'
},
{
id:3,
name:'test3'
}]);
//setup controller scope
scope = {};
ServerCtrl = $controller('ServerCtrl', {
$scope: scope,
$dialog:$dialog
});
});
});
I also prefer a proper mock. When it is not available, i patch the service
To test this:
$dialog.messageBox(title, msg, btns)
.open()
.then(function (result) {
if (result == 'ok') {
// block executed if user click OK
}
});
You can patch $dialog like this:
$dialog.messageBox = function (title, msg, btns) {
return {
open: function () {
return {
then: function (callback) {
callback('ok'); // 'ok' will be set to param result
}
}
}
}
};
Personally I try to mock all services out. If the ui-bootstrap project does not provide a $dialog mock, you should open a bug ticket there and ask them for one. However creating one is as easy.
The mock service should have fake methods that do nothing but return promises. It should also give you a method to flush all asynchronous methods to make it easier to do synchronous testing.
I find it clearest to write my own mock of the dialog. Here's an example of mocking out a dialog to simulate "yes" being chosen.
Code under test
.controller('AdminListingCtrl', function AdminListingController($scope, $dialog, houseRepository) {
$scope.houses = houseRepository.query();
$scope.remove = function (house) {
var dlg = $dialog.messageBox('Delete house', 'Are you sure?', [
{label: 'Yep', result: 'yes'},
{label: 'Nope', result: 'no'}
]);
dlg.open().then(function (result) {
if (result == 'yes') {
houseRepository.remove(house.id);
$scope.houses = houseRepository.query();
}
});
};
}
Tests
describe('when deleting a house', function () {
var fakeDialog = {
open: function()
{
return {
then: function(callback) {
callback("yes");
}
};
}
};
beforeEach(inject(function($dialog) {
spyOn($dialog, 'messageBox').andReturn(fakeDialog);
}));
it('should call the remove method on the houseRepository', function () {
scope.remove({id: 99});
expect(houseRepository.remove).toHaveBeenCalledWith(99);
});
// etc
});