I want to write a directive that keeps a button and page disabled for the duration of the http request.
If I update or submit a form, the button will disable until the http
response is complete.
When a page is loading, it will disable until the entire data is
loaded from the server.
After 10 seconds, the button will be active and the user can click
multiple times.
app.js
<script>
var angModule = angular.module("myApp", []);
angModule.controller("myCtrl", function ($scope, $timeout) {
$scope.isSaving = undefined;
$scope.btnVal = 'Yes';
$scope.save = function()
{
$scope.isSaving = true;
$timeout( function()
{
$scope.isSaving = false;
}, 1000);
};
});
</script>
index.html
<div ng-app="myApp">
<ng-form ng-controller="myCtrl">
Saving: {{isSaving}}
<button ng-click="save()" ng-disabled="isSaving">
<span ng-hide="isSaving">Save</span>
<span ng-show="isSaving">Loading...</span><i class="fa fa-spinner fa-spin" ng-show="isSaving"></i>
</button>
</ng-form>
</div>
I am new to AngularJS, please help me write a directive for this.
here a basic example :
<button ng-click="save()" loading="Loading..." notloading="save" disableonrequest>
myApp.directive("disableonrequest", function($http) {
return function(scope, element, attrs) {
scope.$watch(function() {
return $http.pendingRequests.length > 0;
}, function(request) {
if (!request) {
element.html("<span >"+attrs.notloading+"</span>");
} else {
element.html("<span >"+attrs.loading+"</span><i class='fa fa-spinner fa-spin'></i>");
}
});
}
});
A WORKING EXAMPLE
Depending on your needs, you may not necessarily need a custom directive for this simple task.
You can simply set the $scope.isSaving property inside the callback for $http.
For example
$http({
url: 'http://path/to/the/api/',
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
}
})
.success(function(data, status){
$scope.isSaving = false;
})
.error(....);
Related
How to disable the button untill the button is loaded in AngularJS?
This is my directive for indicate data loading status, and disable button untill $http request is processed.
But the problem is when i reload the page the button will automatically disable and reloaded.How to restrict that?
One more issue.
If I have two more button in the same page when i submit one of that button the entire button will disable and showing loading...
I need two things
When a page is loaded the the other buttons are not disable not showing loading...I want to disable the entire page and currrent submit button should be shown loading...
if one button is submit the other butttons are not showing loading...
This is my code script.js
var myApp = angular.module("myApp", []);
myApp.controller("myCtrl", function($scope, $http, $timeout) {
$scope.save = function() {
$http.pendingRequests.length = 1;
$timeout(function() {
$http.pendingRequests.length = 0;
}, 1000);
};
$scope.submit = function() {
$http.pendingRequests.length = 1;
$timeout(function() {
$http.pendingRequests.length = 0;
}, 1000);
};
});
myApp.directive("disableonrequest", function($http,$timeout)
{
return function(scope, element, attrs)
{
scope.$watch(function()
{
return $http.pendingRequests.length > 0;
}, function(request)
{
if (!request)
{
element.attr('disabled', false);
element.html("<span >" + attrs.notloading + "</span>");
}
else
{
element.attr('disabled', true);
element.html("<span >" + attrs.loading + "</span><i class='fa fa-refresh fa-spin'></i>");
}
});
}
});
view.html
<body ng-app="myApp" ng-controller="myCtrl">
<button ng-click="save()" loading="Loading..." notloading="Save" disableonrequest></button>
<button ng-click="submit()" loading="Loading..." notloading="Submit" disableonrequest></button>
</body>
When i click on each button both button will disable and show loading...
how to restrict that?
I want to disable the entire page when a button is clicked and submitted button should be shown loading...
Please help me. I am new in Angular JS
This happens because your $watch depends on the global variable $http.pendingRequests. And when the value of the variable changes, then angular starts change function for the two directives.
To avoid this, use different variables to store values loading. Example can watch jsfiddle
var myApp = angular.module("myApp", []);
myApp.controller("myCtrl", function($scope, $http, $timeout) {
$scope.saving = false;
$scope.submiting = false;
$scope.save = function() {
$http.pendingRequests.length = 1;
$scope.saving = true;
$timeout(function() {
$scope.saving = false;
$http.pendingRequests.length = 0;
}, 1000);
};
$scope.submit = function() {
$scope.submiting = true;
$http.pendingRequests.length = 1;
$timeout(function() {
$scope.submiting = false;
$http.pendingRequests.length = 0;
}, 1000);
};
});
myApp.directive("disableonrequest", function() {
return {
restrict: 'A',
scope: {
notloading: "#",
loading: "#",
proccess:"=",
},
link: function(scope, element, attrs) {
scope.$watch('proccess', function(request) {
console.log(request,element);
if (!request) {
element.attr('disabled', false);
element.html("<span >" + scope.notloading + "</span>");
} else {
element.attr('disabled', true);
element.html("<span >" + scope.loading + "</span><i class='fa fa-refresh fa-spin'></i>");
}
});
},
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp" ng-controller="myCtrl">
<button ng-click="save()" loading="Loading..." proccess="saving" notloading="Save" disableonrequest></button>
<button ng-click="submit()" loading="Loading..." proccess="submiting" notloading="Submit" disableonrequest></button>
</body>
UPDATED
Solution with block all page.
Live example on jsfiddle
var myApp = angular.module("myApp", []);
myApp.controller("myCtrl", function($scope, $http, $timeout) {
$scope.pendingRequests = $http.pendingRequests;
$scope.save = function() {
$http.pendingRequests.length++;
$timeout(function() {
$http.pendingRequests.length--;
}, 1000);
};
$scope.submit = function() {
$http.pendingRequests.length++;
$timeout(function() {
$http.pendingRequests.length--;
}, 1000);
};
});
myApp.directive("blockWhileLoad", function() {
return {
restrict: 'EA',
replace:true,
transclude:true,
scope: {
proccess: "=",
},
template:'<div><div ng-transclude></div><div ng-class="{\'blocker\':proccess>0}"></div></div>',
link: function(scope, element, attrs) {
},
}
});
.errors {
color: maroon
}
.blocker
{
position: fixed;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px;
z-index: 1038;
background: rgba(0,0,0,.2);
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp" ng-controller="myCtrl">
<fieldset ng-disabled="pendingRequests.length>0">
<input />
<button ng-click="save()">Save</button>
<button ng-click="submit()">Submit</button>
</fieldset>
<block-while-load proccess="pendingRequests.length">
<input />
www.google.com
<button ng-click="save()">Save</button>
<button ng-click="submit()">Submit</button>
</block-while-load>
</body>
Remember, angularjs use promises and the $http service extend $q, so you can use promises with that service.
From: https://docs.angularjs.org/api/ng/service/$http
//Before the request, set element as loading
element.attr('disabled', true);
element.html("<span >" + attrs.loading + "</span><i class='fa fa-refresh fa-spin'></i>");
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// when the http is fully loaded, set visible
element.attr('disabled', false);
element.html("<span >" + attrs.notloading + "</span>");
}, function errorCallback(response) {
// show an error message or just put the button available again
element.attr('disabled', false);
element.html("<span >" + attrs.notloading + "</span>");
});
If I update or submit a form, the button should be disabled until the http response has finished. In the same way, when a page is loading,
the button should be disabled until the entire data is loaded from the server.
In my code, the button is not disabled. How would I go about implementing it?
index.jsp
<body ng-app="myApp" ng-controller="myCtrl">
<button ng-click="save()" loading="Loading..." notloading="save" disableonrequest></button>
</body>
script.js
// the main (app) module
var myApp = angular.module("myApp", []);
// add a controller
myApp.controller("myCtrl", function($scope, $http, $timeout) {
$scope.save = function() {
// JUST FOR TESTING
$http.pendingRequests.length = 1;
$timeout(function() {
$http.pendingRequests.length = 0;
}, 1000);
};
});
myApp.directive("disableonrequest", function($http) {
return function(scope, element, attrs) {
console.log(scope, element, attrs)
scope.$watch(function() {
return $http.pendingRequests.length > 0;
}, function(request) {
console.log(request);
if (!request) {
element.html("<span >" + attrs.notloading + "</span>");
} else {
element.html("<span >" + attrs.loading + "</span><i class='fa fa-spinner fa-spin'></i>");
}
});
}
});
How can I disable the button until it has loaded or the request has started?
You can do this without a directive. Take a look at this JsFiddle.
myApp.controller("myCtrl", function($scope, $http, $timeout) {
$scope.loading = false;
$scope.save = function() {
$scope.loading = true;
//do your ajax request here,
//and in the callback set $scope.loading = false;
$timeout(function() {
$scope.loading = false;
}, 1000);
};
});
If you are using Boostrap here is a great directive
https://github.com/jeremypeters/ng-bs-animated-button
that will do this for you, and change button color and icons, etc....
Edit....
I've updated the Fiddle to actually disable your button.
<button ng-click="save()" ng-disabled="loading">
<span ng-hide="loading">Do some AJAX</span>
<span ng-show="loading"><i class="fa fa-refresh fa-spin"></i></span>
</button>
you can use the ng-disabled directive to disable the button when $scope.loading === true
use ng-disabled="someToggle" inside your button tag, and set the default value of $scope.someToggle=true. then after the $http response happens (like inside .success() ) set $scope.someToggle=false.
I have a user log into my AngularJS app and once they do a http.get retreives a dataset with some key values. One of those is a key that I need to post to a iframe to get it queued up for the right user (based on the key). Thus far I have this and it is not working.
HTML:
<form name="watchLiveForm" action="{{testingUrl}}" target="watch-live" method="post" ng-submit="controllsubmit()">
<label for="key">Company Software Key:</label>
<input type="text" name="key" id="key" ng-model="key">
<input type="submit">
</form>
<iframe name="watch-live" ng-src="{{testingUrl}}" width="100%" height="600"> </iframe>
Controller:
app = angular.module('angularWebApp.indexController', []);
app.controller('indexController', function($scope, $http, $rootScope, $sce) {
$scope.controllsubmit = function() {
console.log("I was called!");
};
if($scope.user !== undefined) {
if($scope.user.software_key.constructor === Array) {
$http.get('http://URL/api/' + $scope.user.software_key[1] + '/remotes').
success(function(data) {
if(data.id === 'error') {
console.log(data);
} else {
$scope.machineList = data;
$scope.key = $scope.user.software_key[1];
console.log($scope.user.software_key[1]);
console.log($scope.key);
$scope.testing = 'http://URL/settings';
$scope.testingUrl = $sce.trustAsResourceUrl($scope.testing);
}
}).
error(function(data) {
alert(data);
});
}
}
});
Directive:
angular.module('angularWebApp.indexDirectives', [])
.directive('form', function() {
return {
require: 'form',
restrict: 'A',
link: function(scope, element, attributes) {
var scopa = element.scope();
if (attributes.name && scopa[attributes.name]) {
scopa[attributes.name].$submit = function() {
// Parse the handler of submit & execute that.
var fn = $parse(attr.ngSubmit);
$scope.$apply(function() {
fn($scope, {$event: e});
});
};
}
}
};
});
After the user is logged in I call a http.get and obtain the software_key which I pass to the form (works). It just seems that getting the form to automatically submit is the issue as I want to made the inputs hidden eventually so they will not see the form as they do now. Any help is greatly appreciated!
I'm developing a simple Ionic mobile app although the answer likely lies with Angular. The app is really simple, displays a list of employees with an Add button which displays a modal, lets the user enter some details, click Save and it's persists the data to a back-end Firebase store. It has 1 controller and a simple service. Initially I had the template html for the modal inside script tags inside the index.html and it all worked fine. When I decided to structure things out and put the modal template in a separate html file, suddenly the data object assigned to ng-modal via the input boxes no longer passes any data to the event handler to save the data, instead it's always undefined. Everything else works as it should, the modal displays ok, the event handlers are calling the right functions etc. The only change is moving the input template to a separate file. I know it's likely something really simple but can't for the life of me work out why and can't find any info about it anywhere else.
Template HTML file for the modal :
<ion-list>
<h1>Add Employee</h1>
<div class="list list-inset">
<ion-item>
<label class="item item-input">
<input type="text" placeholder="Employee Name" ng-model="data.employeeName">
</label>
<label class="item item-input">
<input type="text" placeholder="Employee Age" ng-model="data.employeeAge">
</label>
</ion-item>
<button class="button button-outline button-block button-balanced"
ng-click="addEmployee(true, data)">
Save & Add Another
</button>
<button class="button button-outline button-block button-positive"
ng-click="addEmployee(false, data)">
Save
</button>
<button class="button button-outline button-block button-assertive"
ng-click="closeAddModal()">
Cancel
</button>
</ion-list>
</ion-modal-view>
addEmployee event - data parameter is now always undefined. Worked fine with embedded template :
$scope.addEmployee = function(retainModal, data) {
var employee = {employeeName:data.employeeName,
employeeAge:data.employeeAge};
employeeService.saveEmployee(employee);
if (! retainModal) {
$scope.closeAddModal();
};
data.employeeName = "";
data.employeeAge = "";
};
Based on this question and other needs I create a service that can be useful.
See this post: Ionic modal service or see in operation: CodePen
(function () {
'use strict';
var serviceId = 'appModalService';
angular.module('app').factory(serviceId, [
'$ionicModal', '$rootScope', '$q', '$injector', '$controller', appModalService
]);
function appModalService($ionicModal, $rootScope, $q, $injector, $controller) {
return {
show: show
}
function show(templateUrl, controller, parameters) {
// Grab the injector and create a new scope
var deferred = $q.defer(),
ctrlInstance,
modalScope = $rootScope.$new(),
thisScopeId = modalScope.$id;
$ionicModal.fromTemplateUrl(templateUrl, {
scope: modalScope,
animation: 'slide-in-up'
}).then(function (modal) {
modalScope.modal = modal;
modalScope.openModal = function () {
modalScope.modal.show();
};
modalScope.closeModal = function (result) {
deferred.resolve(result);
modalScope.modal.hide();
};
modalScope.$on('modal.hidden', function (thisModal) {
if (thisModal.currentScope) {
var modalScopeId = thisModal.currentScope.$id;
if (thisScopeId === modalScopeId) {
deferred.resolve(null);
_cleanup(thisModal.currentScope);
}
}
});
// Invoke the controller
var locals = { '$scope': modalScope, 'parameters': parameters };
var ctrlEval = _evalController(controller);
ctrlInstance = $controller(controller, locals);
if (ctrlEval.isControllerAs) {
ctrlInstance.openModal = modalScope.openModal;
ctrlInstance.closeModal = modalScope.closeModal;
}
modalScope.modal.show();
}, function (err) {
deferred.reject(err);
});
return deferred.promise;
}
function _cleanup(scope) {
scope.$destroy();
if (scope.modal) {
scope.modal.remove();
}
}
function _evalController(ctrlName) {
var result = {
isControllerAs: false,
controllerName: '',
propName: ''
};
var fragments = (ctrlName || '').trim().split(/\s+/);
result.isControllerAs = fragments.length === 3 && (fragments[1] || '').toLowerCase() === 'as';
if (result.isControllerAs) {
result.controllerName = fragments[0];
result.propName = fragments[2];
} else {
result.controllerName = ctrlName;
}
return result;
}
} // end
})();
Usage:
appModalService
.show('<templateUrl>', '<controllerName> or <controllerName as ..>', <parameters obj>)
.then(function(result) {
// result from modal controller: $scope.closeModal(result) or <as name here>.closeModal(result) [Only on template]
}, function(err) {
// error
});
You can use another service to centralize the configuration of all modals:
angular.module('app')
.factory('myModals', ['appModalService', function (appModalService){
var service = {
showLogin: showLogin,
showEditUser: showEditUser
};
function showLogin(userInfo){
// return promise resolved by '$scope.closeModal(data)'
// Use:
// myModals.showLogin(userParameters) // get this inject 'parameters' on 'loginModalCtrl'
// .then(function (result) {
// // result from closeModal parameter
// });
return appModalService.show('templates/modals/login.html', 'loginModalCtrl as vm', userInfo)
// or not 'as controller'
// return appModalService.show('templates/modals/login.html', 'loginModalCtrl', userInfo)
}
function showEditUser(address){
// return appModalService....
}
}]);
You need to attach your models to the scope:
$scope.data.employeeName = "";
$scope.data.employeeAge = "";
...and similar every time you reference them.
I need to override the default functionality of the image insert button of the textAngular.
I need to open a modal on the click of the same. How to do this?
I had a similar problem and found it wasn't documented super well. There are a few bug threads where some solutions are mentioned:
https://github.com/fraywing/textAngular/issues/54 and
https://github.com/fraywing/textAngular/issues/146
Following another user's solution there, and the customizing the toolbar section on the wiki, my solution looked like this:
config(['$provide',
function($provide) {
$provide.decorator('taOptions', ['taRegisterTool', '$modal', '$delegate',
function(taRegisterTool, $modal, taOptions) {
// $delegate is the taOptions we are decorating
// here we override the default toolbars specified in taOptions.
taOptions.toolbar = [
['clear', 'h1', 'h2', 'h3'],
['ul', 'ol'],
['bold', 'italics'],
['insertLink', 'insertVideo']
];
// Create our own insertImage button
taRegisterTool('customInsertImage', {
iconclass: "fa fa-picture-o",
action: function($deferred) {
var textAngular = this;
var savedSelection = rangy.saveSelection();
var modalInstance = $modal.open({
// Put a link to your template here or whatever
template: '<label>Enter the url to your image:</label><input type="text" ng-model="img.url"><button ng-click="submit()">OK</button>',
size: 'sm',
controller: ['$modalInstance', '$scope',
function($modalInstance, $scope) {
$scope.img = {
url: ''
};
$scope.submit = function() {
$modalInstance.close($scope.img.url);
};
}
]
});
modalInstance.result.then(function(imgUrl) {
rangy.restoreSelection(savedSelection);
textAngular.$editor().wrapSelection('insertImage', imgUrl);
$deferred.resolve();
});
return false;
},
});
// Now add the button to the default toolbar definition
// Note: It'll be the last button
taOptions.toolbar[3].push('customInsertImage');
return taOptions;
}
]);
}
]);
Check out the plunkr!
Major gotcha: This may be evident to others, but you need the restore selection stuff for the image insert to work. I guess execCommand puts the image at the cursor position, and that position is lost when you open up your own modal. I'm not sure if rangy specifically is necessary, or if you could just make sure the editor has cursor focus first before calling wrapSelection.
Edit: If you're importing the rangy library, textAngular has an optional method in the action constructor for it. Docs: "restoreSelection is only defined if the rangy library is included and it can be called as restoreSelection() to restore the users selection in the WYSIWYG editor." So your action method can use it instead of direct rangy calls.
action: function($deferred, restoreSelection) {
var textAngular = this;
var modalInstance = $modal.open({
....
});
modalInstance.result.then(function(imgUrl) {
restoreSelection();
textAngular.$editor().wrapSelection('insertImage', imgUrl);
$deferred.resolve();
});
return false;
},
});
...
Im' sharing the code for an image upload service I implemented through angular-bootsrap-ui $modal service and DanialFarid's angular-file-upload service.
.config(function ($provide) {
$provide.decorator('taOptions', ['taRegisterTool', '$delegate', '$modal', function (taRegisterTool, taOptions, $modal) {
taRegisterTool('uploadImage', {
iconclass: "fa fa-image",
action: function (deferred) {
$modal.open({
controller: 'UploadImageModalInstance',
templateUrl: 'views/modals/upload.html'
}).result.then(
function (result) {
document.execCommand('insertImage', true, result);
deferred.resolve();
},
function () {
deferred.resolve();
}
);
return false;
}
});
taOptions.toolbar[1].push('uploadImage');
return taOptions;
}]);
})
Now the view for the modal with the button that handles the $scope.upload() function, previews the uploaded image, and a button that fires the $scope.insert() function.
<form data-ng-submit="insert()">
<div class="modal-header">
<div class="col-md-10">
<h4 class="modal-title"><span class="glyphicon glyphicon-picture"></span> Upload Image</h4>
</div>
<div class="col-md-2 right">
<a class="close" href data-ng-click="$dismiss()"><span class="glyphicon glyphicon-remove"></span></a>
</div>
</div>
<div class="modal-body">
<img data-ng-src="{{image}}">
<progressbar data-ng-show="progress > 0 && progress < 100" class="progress-striped active" value="progress"></progressbar>
</div>
<div class="modal-footer center clearfix">
<button data-ng-click="$dismiss()" type="button" class="btn btn-default pull-left">Cancel</button>
<button type="submit" data-ng-disabled="image == null || image == '' || image == 'img/posts/default.svg'" class="btn btn-primary pull-right">Insert Image</button>
<button type="button" data-ngf-select data-ngf-change="upload()" data-ng-model="files" data-ngf-multiple="false" class="btn btn-primary pull-right"><span class="glyphicon glyphicon-picture mR5"></span>Upload Image</button>
</div>
</form>
And also the controller that handles the file upload and image insert:
app.controller('UploadImageModalInstance', function($scope, $modalInstance, Upload){
$scope.image = 'img/default.png';
$scope.progress = 0;
$scope.files = [];
$scope.upload = function(){
Upload.upload({
url: 'api/upload',
fields: {'dir': 'img/uploads/'},
file: $scope.files[0],
method: 'POST'
}).progress(function (evt) {
$scope.progress = parseInt(100.0 * evt.loaded / evt.total);
}).success(function (data) {
$scope.progress = 0;
$scope.image = data.dir+data.filename;
});
};
$scope.insert = function(){
$modalInstance.close($scope.image);
};
})