Why I cannot change a value of angular property in html? - angularjs

I have a tooltip which popups on focus using angular popovers. What I need to do is when I click a button, to change it to popup on blur. It changes but doesn't change the tooltip behavior.
Here is the plunkr: http://plnkr.co/edit/L1oeZQrQdF0AdJMKVsG6?p=preview
Below is the code:
<input type="text"
ng-model="value"
value="{{value}}"
uib-popover-template="htmlPopover"
popover-trigger="{{triggerOn}}"
popover-popup-close-delay="1000"
class="form-control">
<script type="text/ng-template" id="myPopoverTemplate.html">
<div>
<button ng-click="test()"><b style="color: red">Add message</b></button>
</div>
</script>
controller
function ($scope) {
$scope.value = "Click me!";
$scope.dynamicPopover = {
content: 'Hello, World!',
templateUrl: 'myPopoverTemplate.html',
title: 'Title'
};
$scope.message = 'Trigger: none';
$scope.triggerOn = "focus";
$scope.changeTrigger = function(){
$scope.triggerOn = "blur";
$scope.message = "Should trigger on blur";
}
$scope.test = function(){
$scope.value = "test me click";
}
$scope.htmlPopover = 'myPopoverTemplate.html';
});

Looking at the source code for the uib-popover directive, one can that the triggers are set when the directive is linked.
compile: function(tElem, tAttrs) {
var tooltipLinker = $compile(template);
return function link(scope, element, attrs, tooltipCtrl) {
var tooltip;
var tooltipLinkedScope;
var transitionTimeout;
var showTimeout;
var hideTimeout;
var positionTimeout;
var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
//TRIGGERS SET AT LINK TIME
var triggers = getTriggers(undefined);
var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
var ttScope = scope.$new(true);
var repositionScheduled = false;
var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
var observers = [];
var lastPlacement;
If one wants to change how the directive triggers, the directive needs to be re-compiled with the $compile service on each change to the triggers.

Related

Test case for focus-next custom directive in Angular JS

I created a custom directive to resolve some focus related issues in my application.
Directive Code:
(function() {
angular.module("FocusNextModule", []).directive("focusNext", function() {
return {
restrict: "A",
link: function($scope, elem, attrs) {
elem.bind("focus", function(e) {
var code = e.which || e.keyCode;
var nextElem = document.getElementById(attrs.focusNext);
if (nextElem === null || nextElem === undefined) {
var altElem = document.getElementById(attrs.focusNextAlt);
if (angular.element(altElem).hasClass('ng-hide') === false) {
altElem.focus();
} else {
var selfElem = document.getElementById(attrs.focusSelf);
selfElem.focus();
}
e.preventDefault();
} else {
nextElem.focus();
e.preventDefault();
}
});
}
};
});
})();
How to use in template Use InT emplate
<md-button id="idOfElementC">MyButton</md-button>
<div tabindex="0" focus-next="idOfElementA" focus-next-alt="idOfElementB" focus-self="idOfElementC"></div>
Note:
Element with "idOfElementC" id will be just above the div using focus-next directive.
How does the directive work?
When we press tab on element with "idOfElementC" id (here button), focus will go to div using focus-next directive. The div will redirect the focus to other elements using following cases:
a) First it will check if there is any element with id "idOfElementA". If element exists, then that element will receive focus.
b) If element with id "idOfElementA" do not exist, then "idOfElementB" will receive focus.
c) If element with id "idOfElementB" do not exist as well, then finally "idOfElementA" (on which tab was pressed) will receive focus.
The directive is working fine and fixing all my issues. But, I need to write jasmine test cases for this directive.
Can anyone guide me how to write Jasmine test cases for focus?
UPDATE:
As per comment of #PetrAveryanov the directive was looking horrible and I completely agree.
Updated Directive:
(function() {
angular.module("FocusNextModule", []).directive("focusNext", function() {
return {
restrict: "A",
link: function($scope, elem, attrs) {
elem.bind("focus", function(e) {
var elemToFocus = document.getElementById(attrs.focusNext) || document.getElementById(attrs.focusNextAlt);
/*jshint -W030 */
angular.element(elemToFocus).hasClass('ng-hide') === false ? elemToFocus.focus() : document.getElementById(attrs.focusSelf).focus();
e.preventDefault();
});
}
};
});
})();
Finally, got it how to write test cases for the directive.
describe('focus-next-directive test', function() {
var compile, scope;
beforeEach(module(FocusNextModule));
beforeEach(inject(function($compile, $rootScope) {
compile = $compile;
scope = $rootScope.$new();
}));
it('should focus the next element', function() {
var div = compile('<div tabindex="0" focus-next="idTestNext"/>')(scope);
var nextElem = compile('<input id="idTestNext" type="text" />')(scope);
angular.element(document.body).append(div);
angular.element(document.body).append(nextElem);
div.focus();
expect(nextElem).toEqual(angular.element(document.activeElement));
div.remove();
nextElem.remove();
});
it('should focus the next alternative element', function() {
var div = compile('<div tabindex="0" focus-next="idTestNext" focus-next-alt="idTestNextAlt"/>')(scope);
var nextAltElem = compile('<input id="idTestNextAlt" type="text" />')(scope);
angular.element(document.body).append(div);
angular.element(document.body).append(nextAltElem);
div.focus();
expect(nextAltElem).toEqual(angular.element(document.activeElement));
div.remove();
nextAltElem.remove();
});
it('should focus the Self element', function() {
var selfElem = compile('<input id="idTestSelf" type="text" ng-class="ng-hide"/>')(scope);
var div = compile('<div tabindex="0" focus-next="idTestNext" focus-next-alt="idTestNextAlt" focus-self="idTestSelf"/>')(scope);
var nextAltElem = compile('<input id="idTestNextAlt" type="text" class="ng-hide"/>')(scope);
angular.element(document.body).append(selfElem);
angular.element(document.body).append(div);
angular.element(document.body).append(nextAltElem);
div.focus();
expect(selfElem).toEqual(angular.element(document.activeElement));
div.remove();
selfElem.remove();
nextAltElem.remove();
});
});

Angular Typeahead dynamically added results

I have having difficulties getting the uib-typeahead to work as I need it to.
See: https://jsfiddle.net/0wp1t0ut/ - if you type "g" into the input box, "germany" correctly gets added to the source array, but the typeahead view does not get updated until the next keypress. I.e. Germany is there, but I can't select it until I press the "e".
Basically what I'm trying to achieve is a typeahead that will dynamically update as new results come in (rather than having to wait for all my calls to complete before I return the array to the typeahead).
[Note in my real code, the ng-change on the typeahead is a function that makes multiple calls to different sources, and I want the typeahead to show with data as soon as the first call returns data, and add to that as later calls return more results.]
Has anyone come across this problem before, or able to offer any suggestions (happy to use a different typeahead if a better alternative already exists!)
HTML:
<div ng-app="myApp" ng-controller="MyCtrl as vm">
<label>TypeAhead:</label>
<input type="text" ng-model="selected" ng-change="vm.add()" uib-typeahead="state as state.descrizione for state in vm.states | filter:$viewValue | limitTo:8" typeahead-model-change class="form-control" >
<label>Model</label>
<pre>{{vm.states|json}}</pre>
<label>Modify Model Description:</label>
<input ng-model="selected.descrizione" class="form-control">
</div>
JS:
var myApp = angular.module('myApp', ['ui.bootstrap']);
myApp.controller('MyCtrl', [function() {
var vm = this;
vm.add = function(){
var a = {
codice: 'at',
descrizione: 'germany'};
vm.states.push(a)
}
vm.states = [{
codice: 'it',
descrizione: 'italia'
}, {
codice: 'fr',
descrizione: 'francia'
}];
}]);
myApp.directive('typeaheadModelChange', function() {
return {
require: ['ngModel', 'typeaheadModelChange'],
controller: ['$scope', '$element', '$attrs', '$transclude', 'uibTypeaheadParser', function($scope, $element, $attrs, $transclude, uibTypeaheadParser) {
var watchers = [];
var parserResult = uibTypeaheadParser.parse($attrs.uibTypeahead);
var removeWatchers = function() {
angular.forEach(watchers, function(value, key) {
value();
});
watchers.length = 0;
}
var addWatchers = function(modelCtrl) {
watchers.push($scope.$watch('selected', function(newValue, oldValue) {
if (oldValue === newValue)
return;
if (newValue) {
var locals = [];
locals[parserResult.itemName] = newValue;
$element.val(parserResult.viewMapper($scope, locals));
}
}, true));
}
this.init = function(modelCtrl) {
modelCtrl.$formatters.push(function(modelValue) {
removeWatchers();
addWatchers(modelCtrl);
return modelValue;
});
};
}],
link: function(originalScope, element, attrs, ctrls) {
ctrls[1].init(ctrls[0]);
}
};
});

If I use ControllerAs to use a controller twice on a page, how can the controller code tell WHICH one it's dealing with?

Complete noob with Angular and the fact that I am asking this question (to which I cannot find an answer after two days) suggests to me that I am approaching this all wrong :/
I am using the DatePicker controller twice on the same page. So I have:
<div ng-controller="DatepickerDemoCtrl as fromdate">
...
</div>
<div ng-controller="DatepickerDemoCtrl as todate">
...
</div>
So in my js code for the controller, how do I know which datepicker has "called" it because I want to save the value in the appropriate (from or to)cookie?
app.controller('DatepickerDemoCtrl', function ($scope, $log) {
this.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
this.format = this.formats[0];
this.today = function() {
this.value = new Date();
};
this.today();
this.clear = function () {
this.value = null;
};
this.opened = false;
this.dpopen = function ($event) {
$log.info("DPOpened " )
$event.preventDefault();
$event.stopPropagation();
this.opened = true;
};
this.dateOptions = {
formatYear: 'yy',
startingDay: 1
};
As I say, I expect I need to understand some simple point to do this right.
1
There are for me 2 options for doing this. The easiest one is to call a function of your controller inside ng-init.
add this into your controller :
var self = this;
this.isToOrFrom = function(toOrFrom){
self.isTo = (toOrFrom === "to");
};
and call this from your template :
<div ng-controller="DatepickerDemoCtrl as date" ng-init="date.isToOrFrom('to')">
...
</div>
<div ng-controller="DatepickerDemoCtrl as date" ng-init="date.isToOrFrom('from')"">
...
</div>
ps : Angular does not recommend to use ng-init
2
The second option (the one I advice) is to wrap your controller into a directive, and add an additional parameter.
app.directive('datePicker', function(){
return : {
restrict: 'AE',
scope:{
isToOrFrom: '#',
},
controller : function ($scope, $log) {
this.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
this.format = this.formats[0];
this.today = function() {
this.value = new Date();
};
this.today();
this.clear = function () {
this.value = null;
};
this.isTo = ($scope.isToOrFrom === "to");
this.opened = false;
this.dpopen = function ($event) {
$log.info("DPOpened " )
$event.preventDefault();
$event.stopPropagation();
this.opened = true;
};
this.dateOptions = {
formatYear: 'yy',
startingDay: 1
};
},
controllerAs : 'date',
template: '<div>add your template here</div>'
});
And then, you just have to use it like the following in your view :
<div date-picker is-to-or-from="to" />

compile textbox using angularjs directive

I want to compile textbox using directive in angularjs.
like custom textbox. can anyone help me?
when i am trying to complie line 1 textbox with tow argument it is problem.
Thanks in advance.
see on line no 1 -----------
it is tutorialspoint example i change some content in that.
<div ng-app="mainApp" ng-controller="StudentController">
<student name="Mahesh"></student><br/>
<student name="Piyush"></student>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script>
var mainApp = angular.module("mainApp", []);
mainApp.directive('student', function() {
var directive = {};
directive.restrict = 'E';
directive.template = "Student: <b>{{student.name}}</b> , Roll No: <b>{{student.rollno}} </b> <br/> <input name='{{student.mytype}}' type='{{student.mytype}}'> ";
directive.scope = {
student : "=name"
}
directive.compile = function(element, attributes) {
// element.css("border", "1px solid #cccccc");
var linkFunction = function($scope, element, attributes) {
element.html("Student: <b>"+$scope.student.name +"</b> , Roll No: <b>"+$scope.student.rollno+"</b><br/>" +
"<b> <input type='"+$scope.student.mytype+"' name='+$scope.student.mytype+' > "); -----line 1
//element.css("background-color", "#ff00ff");
}
return linkFunction;
}
return directive;
});
mainApp.controller('StudentController', function($scope) {
$scope.Mahesh = {};
$scope.Mahesh.name = "Mahesh Parashar";
$scope.Mahesh.rollno = 1;
$scope.Mahesh.mytype = "email";
$scope.Piyush = {};
$scope.Piyush.name = "Piyush Parashar";
$scope.Piyush.rollno = 2;
$scope.Piyush.mytype = "password";
});
</script>
I just updated the code like below. The issue was with the b tag, you didn't close it also you forgot to add double quotation in the name attribute.
var linkFunction = function($scope, element, attributes) {
element.html("Student: <b>"+$scope.student.name +"</b> , Roll No: <b>"+$scope.student.rollno+"</b><br/>" +
"<b> <input type='"+$scope.student.mytype+"' name='"+$scope.student.mytype+"' /></b> ");
}

ng-select with lazy population

It's an edit form with parent record first populated then dependent select list is populated, and then it's expected the value from parent record pre-select the combo box.
html
<select ng-model="data.trackId" >
<option ng-repeat="track in tracks" value="{{track.id}}">{{track.name}}</option>
initial result once parent record is pulled.
if(data) {
this.$scope.data.id = data.id;
this.$scope.data.name = data.name;
this.$scope.data.room = data.room;
this.$scope.data.start = data.start;
this.$scope.data.end = data.end;
this.$scope.data.dayId = data.day_id;
this.$scope.data.trackId = data.track_id;
this.$scope.data.color = data.color;
this.$scope.data.description = data.description;
this.$scope.$apply();
this.$element[0].removeAttribute("style");
}
//later track results were pulled
trackResult: function(data, status, headers, config) {
for(var i=0; i<data.length; i++) {
this.$scope.tracks.push(data[i]);
}
this.$scope.$apply();
},
Problem:
List gets populated from the second call trackResult but default value from the $scope.trackId never sets the combo box to a value.
Edit: Controller Body
controller: function($scope, $element) {
var self = this;
this.$scope = $scope;
this.$element = $element;
this.$scope.data = {};
this.$scope.days = [];
this.$scope.tracks = [];
this.$scope.submit = function() {self.submit()};
this.$scope.cancel = function() {self.cancel()};
},
Edit : Updated with setting the data from outside the scope (OP request)
Use ng-options & ng-model
this is how i think it should be done in angularjs.
use the built in databinding capabilities to simplify your code and make it less complicated
for binding a list into a <select> and controlling the selected item, this snippet below should do the trick.
http://jsfiddle.net/72em40j4/
js
var myapp = angular.module('myapp', []);
myapp.controller('Ctrl', function ($scope) {
$scope.options = [];
$scope.selectedOption = null;
});
html
<script>
function clickFromOutside() {
var controllerElement = document.getElementById('container');
var controllerScope = angular.element(controllerElement).scope();
var firstTrack = {
id: 1,
first: 'First',
last: 'Track'
};
var secondTrack = {
id: 2,
first: 'Second',
last: 'Track'
};
controllerScope.options.push(firstTrack);
controllerScope.options.push(secondTrack);
controllerScope.selectedOption = secondTrack;
controllerScope.$apply();
}
</script>
<button onclick="clickFromOutside();">outside</button>
<div ng-app="myapp">
<fieldset id="container" ng-controller="Ctrl">
<select ng-options="p.first + ' ' + p.last for p in options" ng-model="selectedOption"></select> <pre>{{ selectedOption }}</pre>
</fieldset>
</div>

Resources