Here is what I am trying to do:
I have a directive with graphs that ng-repeat data from a service array.
When I go from one page using the directive to another the directive has information from the first page. So if compliance is at 80% for person1 when I go to see person2 with a compliance of 50% I still see person1's information.
I tried $state.reload() which started an infinite loop.
I tried clearing the scope variables on $rootScope.$on($stateChangeLeave) etc.
Here is what my html looks like ( the compliance-gauge is the directive):
<div class="col-md-4" ng-repeat="gauge in regulatorDict[license.reg_id].gauges">
<compliance-gauge license="regulatorDict"
id="license.reg_id + gauge.name"
key="gauge.name"
value="license.applied[gauge.value]"
max="gauge.max"></compliance-gauge>
Here is the directive js file:
export default function() {
'ngInject';
'use strict';
return {
restrict: 'E',
replace: false,
scope: {
key: '=',
id: '=',
value: '=',
max: '='
},
templateUrl: template,
controller: function($scope,$state) {
'ngInject';
let percentage = ($scope.value / $scope.max) * 100;
let gaugeColors = ['#72C02C', '#f8ac59', '#ED5565'];
$scope.colorDict = {};
$scope.newId = $scope.id.split(' ')[0];
if (percentage >= 66) {
$scope.colorDict[$scope.newId] = gaugeColors[0];
} else if (percentage >= 33 && percentage < 66) {
$scope.colorDict[$scope.newId] = gaugeColors[1];
} else {
$scope.colorDict[$scope.newId] = gaugeColors[2];
}
}
};
}
Any suggestions?
Related
I am working on MEAN stack application in which I am defining a drop down using following customize directive. This drop down shows available variables from an array(around 70K options).
.directive('inputDropdown', function($parse) {
var template =
'<input class="form-control" ng-model="ngModel" ng-disabled="disabled" ng-focus="setDirectiveList()" ng-blur="removeDirectiveList()">' +
'<div class="dropdown dropdown1" input-dropdown="increaseLimit()" ng-init="limit=20;" ng-click="setDirectiveList()">' +
'<div class="form-control" ng-repeat="value in selectedList | filter:ngModel | limitTo:limit">' +
'<div ng-mousedown="select($event, value)">{{value}}</div>' +
'</div>' +
'</div>';
return {
restrict: 'EA',
require: '^form',
scope: {
ngModel: '=',
list: '=',
onSelect: '&',
disabled:'=ngDisabled'
},
template: template,
link: function(scope, element, attrs,mapController) {
element.addClass('input-dropdown');
var handler = $parse(attrs.inputDropdown);
element.scroll(function (evt) {
var scrollTop = element[0].scrollTop,
scrollHeight = element[0].scrollHeight,
offsetHeight = element[0].offsetHeight;
if (scrollTop === (scrollHeight - offsetHeight)) {
$scope.$apply(function () {
handler($scope);
});
}
});
if(scope.$parent.setDirty)
{
scope.makeFormDirty = mapController.$setDirty();
}
scope.select = function(e, value) {
scope.ngModel = value;
// scope.onSelect({$event: e, value: value});
scope.makeFormDirty = mapController.$setDirty();
};
scope.setDirectiveList = function() {
// debugger;
scope.selectedList = scope.list;//scope.$parent.variables;
}
scope.removeDirectiveList = function() {
scope.selectedList = [];
}
}
};
})
What I am trying to do is using limitTo option, increasing the the display variables count by some value when the scroll bar of drop down hits bottom. The increaseLimit function is defined in my controller as:
$scope.increaseLimit = function () {
if ($scope.limit < $scope.variables.length) {
$scope.limit += 20;
}
};
where $scope.variables contains 70K entries. I am not sure whether the I am adding increaseLimit to correct div or scrolling function is wrong.
I want to achieve this type of loading in my drop down. How to add this in my customize directive input-dropdown. I tried but no success.
Please Help.
I have a directive to upload a file to the browser
angular.module('angularPrototypeApp')
.directive('upload', ['$parse', function ($parse) {
return {
restrict: 'A',
scope: false,
link: function(scope, ele, attrs) {
var fn = $parse(attrs.upload);
ele.on('change', function(onChangeEvent){
var reader = new FileReader();
reader.onload = function(onLoadEvent) {
scope.$apply(function(){
fn(scope, {$fileContents: onLoadEvent.target.result} );
});
}
reader.readAsText((onChangeEvent.srcElement || onChangeEvent.target).files[0]);
})
}
};
}]);
The code is taken from here:
https://veamospues.wordpress.com/2014/01/27/reading-files-with-angularjs/
The view looks like this:
<progressbar class="progress-striped active" value="dynamic" </progressbar>
<input type="file" upload="parseInputFile($fileContents)">
In the controller I do the following:
angular.module('angularPrototypeApp')
.controller('Gc2xlsxCtrl', ['$scope', 'blockUI', function ($scope, $timeout, blockUI) {
$scope.dynamic = 0;
$scope.parseInputFile = function($fileContents){
$scope.$broadcast('fileUploaded', $fileContents);
}
$scope.$on('fileUploaded', function(event, fileContents) {
if(fileContents !== undefined){
blockUI.start();
//a lot of work is done herem takes between 2 and 20 Seconds
for(var i = 1; i <= 100; i++){
$scope.dynamic += 1;
}
blockUI.stop();
}
});
}]);
My problem is that the update to $scope.dynamic is shown in the view only after the whole method has finished. The same is true for blockUI. The logs say that it's called right at the beginning of the method, but the view is never upated.
Any help on this would be fantastic!
This part:
//a lot of work is done here, takes between 2 and 20 Seconds
for(var i = 1; i <= 100; i++){
$scope.dynamic += 1;
}
Is synchronous code and won't update the view until it's done. Basically, the loop updates $scope.dynamic from 1 to 100, but the view can't change until it's done with that; so you just see it at 1, then at 100.
You need to have the work done asynchronously to allow the view to be updated while the work is happening.
How best to go about doing that is another question. You could use $q in AngularJS to do it.
I need to increase the corresponding value, on the click of + button,
but at the same time, on click of any reset button, i need to reset all the values to 100.
Currently, i am able to increase the corresponding value on the click of + button, but on click of reset button, only the values that have not been incremented gets reset to 100
Basically i need to access a single value of f(for incrementation) and also all the values of f together(for resetting)
How do i implement this.
A demo of this problem is available here in plunkr
HTML snippet
<body ng-controller="MainCtrl">
<counter-widget startnumber=1 resetter="reset"></counter-widget>
</body>
JS snippet
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $timeout) {
$scope.triggerReset = function () {
$scope.reset = true;
console.log('reset')
$timeout(function() {
$scope.reset = false;
},100)
}
});
app.directive("counterWidget",function(){
return{
restrict:"E",
scope:{
startnumber: '=',
resetter: '='
},
link:function(scope,elem,attr){
scope.f = 1;
scope.add = function(f){
this.f ++
}
scope.reset = function(){
scope.$parent.triggerReset()
}
scope.$watch(function(attr) {
return scope.resetter
},
function(newVal) {
if (newVal === true) {
scope.f = 100;
}
})
},
template:'<li ng-repeat="item in [1,2,3]">'+
"<button ng-click='add(f)'>+</button>"+
"{{f}}   "+
"<button ng-click='reset()'>reset</button><br><br>"+
'</li>'
}
})
ngRepeat creates a new scope for each item, so you get an new f for each item. To get access to all values I propose to store the values in an array and bind to this array.
http://plnkr.co/edit/amGSHZqCWsFgjl2bEM98
app.controller('MainCtrl', function($scope, $timeout) {
$scope.reset = function() {
$scope.values = []
for(var i = 0; i < 100; ++i) {
$scope.values.push({f: i});
}
}
$scope.reset();
});
app.directive("counterWidget",function(){
return{
scope:{
values: '=',
reset: '='
},
link:function(scope, elem, attr){
scope.add = function(value){
value.f++;
}
},
template:'<li ng-repeat="item in values">'+
"<button ng-click='add(item)'>+</button>"+
"{{item.f}}   "+
"<button ng-click='reset()'>reset all</button><br><br>"+
'</li>'
}
})
I want to load a state as a modal so that I can overlay a state without effecting any other states in my application. So for example if I have a link like:
<a ui-sref="notes.add" modal>Add Note</a>
I want to then interrupt the state change using a directive:
.directive('modal', ['$rootScope', '$state', '$http', '$compile',
function($rootScope, $state, $http, $compile){
return {
priority: 0,
restrict: 'A',
link: function(scope, el, attrs) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
event.preventDefault();
});
el.click(function(e){
$http
.get('URL HERE')
.then(function(resp){
$('<div class="modal">' + resp.data + '</div>').appendTo('[ui-view=app]');
setTimeout(function(){
$('.wrapper').addClass('showModal');
},1);
});
});
}
}
}
])
This successfully prevents the state change and loads the URL and appends it as a modal to the application. The problem is that it loads the entire application again...
How can I load just the state? e.g. the template files and the adjoining controller.
The state looks like:
.state('notes.add',
{
parent: 'notes',
url: '/add',
views: {
'content': {
templateUrl: 'partials/notes/add.html',
controller: 'NotesAddCtrl'
}
}
})
An example of how it should work using jQuery: http://dev.driz.co.uk/AngularModal
See how I can access StateA and StateB loading via AJAX that uses the History API to change the URL to reflect the current state change.
And regardless of whether I am on the index, StateA or StateB I can load StateA or StateB as a modal (even if I'm on that State already) and it doesn't change the url or the current content, it just overlays the state content.
This is what I want to be able to do in AngularJS.
Note. this example doesn't work with the browser back and forward buttons due to it being a quick example and not using the history api correctly.
I've seen your question a few days ago and it seemed interesting enough to try and set up something that would work.
I've taken the uiSref directive as a start, and modified the code to use angular-bootstrap's $modal to show the desired state.
angular.module('ui.router.modal', ['ui.router', 'ui.bootstrap'])
.directive('uiSrefModal', $StateRefModalDirective);
function parseStateRef(ref, current) {
var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
if (preparsed) ref = current + '(' + preparsed[1] + ')';
parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
return { state: parsed[1], paramExpr: parsed[3] || null };
}
function stateContext(el) {
var stateData = el.parent().inheritedData('$uiView');
if (stateData && stateData.state && stateData.state.name) {
return stateData.state;
}
}
$StateRefModalDirective.$inject = ['$state', '$timeout', '$modal'];
function $StateRefModalDirective($state, $timeout, $modal) {
var allowedOptions = ['location', 'inherit', 'reload'];
return {
restrict: 'A',
link: function(scope, element, attrs) {
var ref = parseStateRef(attrs.uiSrefModal, $state.current.name);
var params = null, url = null, base = stateContext(element) || $state.$current;
var newHref = null, isAnchor = element.prop("tagName") === "A";
var isForm = element[0].nodeName === "FORM";
var attr = isForm ? "action" : "href", nav = true;
var options = { relative: base, inherit: true };
var optionsOverride = scope.$eval(attrs.uiSrefModalOpts) || {};
angular.forEach(allowedOptions, function(option) {
if (option in optionsOverride) {
options[option] = optionsOverride[option];
}
});
var update = function(newVal) {
if (newVal) params = angular.copy(newVal);
if (!nav) return;
newHref = $state.href(ref.state, params, options);
if (newHref === null) {
nav = false;
return false;
}
attrs.$set(attr, newHref);
};
if (ref.paramExpr) {
scope.$watch(ref.paramExpr, function(newVal, oldVal) {
if (newVal !== params) update(newVal);
}, true);
params = angular.copy(scope.$eval(ref.paramExpr));
}
update();
if (isForm) return;
element.bind("click", function(e) {
var button = e.which || e.button;
if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
e.preventDefault();
var state = $state.get(ref.state);
var modalInstance = $modal.open({
template: '<div>\
<div class="modal-header">\
<h3 class="modal-title">' + ref.state + '</h3>\
</div>\
<div class="modal-body">\
<ng-include src="\'' + state.templateUrl + '\'"></ng-include>\
</div>\
</div>',
controller: state.controller,
resolve: options.resolve
});
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
}
});
}
};
}
You can use it like this <a ui-sref-modal="notes.add">Add Note</a>
Directive requires the angular-bootstrap to resolve the modal dialog. You will need to require the ui.router.modal module in your app.
Since asked to provide an example for my comment,
Example Directive
myapp.directive('openModal', function ($modal) {
return function(scope, element, attrs) {
element[0].addEventListener('click', function() {
$modal.open({
templateUrl : attrs.openModal,
controller: attrs.controller,
size: attrs.openModalSize,
//scope: angular.element(element[0]).scope()
});
});
};
});
Example Html
<button
open-modal='views/poc/open-modal/small-modal.html'
open-modal-size='sm'
controller="MyCtrl">modal small</button>
The above directive approach is not very different from using a state, which has templateUrl and controller except that url does not change.
.state('state1.list', {
url: "/list",
templateUrl: "partials/state1.list.html",
controller: function($scope) {
$scope.items = ["A", "List", "Of", "Items"];
}
})
Apparently there is the issue Ui-sref not generating hash in URL (Angular 1.3.0-rc.3) refering to
https://github.com/angular-ui/ui-router/issues/1397
It is seems to be fixed as per comments.
I personally dislike html5mode because it requires extra work on your server for no apparent advantage (I don't regard having "more beautiful url" as tangible advantage to justify the extra work).
There is another performance problem when using routers, that the view DOM is re-created upon each route change. I mentioned a very simple solution
in this answer.
As a side remark, the example in http://dev.driz.co.uk/AngularModal/ does not behave quite well. It does not record history, so I can't go back. Further, if you click on links like Index or modals, and then reload, you don't get the same page.
UPDATE.
It seems from the comments that a route change is not wanted when opening the modal. In that case the easiest solution is not to put ui-sref on the opening button and let the modal directive along handle it.
I have an interesting situation.
I have a directive with isolate scope that generate list of numbers and the user can choose numbers like in lottery.
The problem i have is that i required minimum of 1 line, if the user pick only one line so when he click play i want to auto trigger the next directive in the ng-repeat to pick for him numbers, I made this plunker so you guys can understand better and help me.
http://plnkr.co/edit/vWGmSEpinf7wxRUnqyWq?p=preview
<div ng-repeat="line in [0,1,2,3]">
<div line line-config="lineConfig">
</div>
</div>
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.lineConfig = {
guessRange: 10
}
$scope.lines = [];
$scope.$on('lineAdded', function(event, line) {
$scope.lines.push(line);
});
$scope.play = function() {
/// here i want to check if $scope.lines.length
//is less then one if yes then auto trigger the next
//line directive to auto do quick pick and continue
}
})
.directive('line', function() {
return {
restrict: 'A',
templateUrl: 'line.html',
scope: {
lineConfig: '='
},
link: function($scope, elem, attr) {
var guessRange = $scope.lineConfig.guessRange;
$scope.cells = [];
$scope.line = {
nums: []
};
$scope.$watch('line', function(lotLine) {
var finaLine = {
line: $scope.line
}
if ($scope.line.nums.length > 4) {
$scope.$emit('lineAdded', finaLine);
}
}, true);
_(_.range(1, guessRange + 1)).forEach(function(num) {
$scope.cells.push({
num: num,
isSelected: false
});
});
$scope.userPickNum = function(cell) {
if (cell.isSelected) {
cell.isSelected = false;
_.pull($scope.lotLine.nums, cell.num);
} else {
cell.isSelected = true;
$scope.lotLine.nums.push(cell.num);
}
};
$scope.quickPick = function() {
$scope.clearLot();
$scope.line.nums = _.sample(_.range(1, guessRange + 1), 5);
_($scope.line.nums).forEach(function(num) {
num = _.find($scope.cells, {
num: num
});
num.isSelected = true;
});
}
$scope.clearLot = function() {
_($scope.cells).forEach(function(num) {
num.isSelected = false;
});
$scope.line.nums = [];
}
}
}
})
You could pass the $index (exists automatically in the ng-repeat scope) - variable into the directive and cause it to broadcast an event unique for ($index + 1) which is the $index for the next instance.
The event could be broadcasted from the $rootScope or a closer scope that's above the repeat.
Then you could capture the event in there.
Probably not the best way to do it.
I can try to elaborate if anything is unclear.
EDIT
So I played around alittle and came up with this:
http://plnkr.co/edit/ChRCyF7yQcN580umVfX1?p=preview
Rather
Rather than using events or services I went with using a directive controller to act as the parent over all the line directives inside it:
.directive('lineHandler', function () {
return {
controller: function () {
this.lines = [];
}
}
})
Then requiring 'lineHandler' controller inside the 'line' directive - the controller being a singleton (same instance injected into all the line directives) - you can then setup that controller to handle communication between your directives.
I commented most of my code in the updated plnkr and setup an example of what I think you requested when clicking in one list - affecting the one beneath.
I hope this helps and if anything is unclear I will try to elaborate.