Focus is not working by index in AngularJS - angularjs

I have a list of items which have update text field with some words, I want to get the focus at end of the words on the selected index. but every time I am getting focus on last index.
Here is my HTML code
<body ng-controller="MainCtrl">
<h1>Hello Plunker!</h1>
<li ng-repeat="i in number">
<input focus-on="focusMe" value="sfsfsf" />
<button type="submit" ng-click="do()">do</button>
</li>
</body>
Here my controller:
app.directive('focusOn', function() {
return function(scope, elem, attr) {
scope.$on('focusOn', function(e, name) {
if(name === attr.focusOn) {
elem[0].focus();
}
});
};
});
app.factory('focus', function ($rootScope, $timeout) {
return function(name) {
$timeout(function (){
$rootScope.$broadcast('focusOn', name);
});
}
});
app.controller('MainCtrl', function ($scope, focus) {
$scope.number=[1,2,3,4,5,6,7,8,9,10];
$scope.do=function(){
focus('focusMe');
}
Here is my plunker.

Correct solution will probably depend on what browsers you need to support.
You can try this:
scope.$on('focusOn', function(e, name) {
if (name == attr.name) {
var element = elem[0];
element.focus();
var value = element.value;
element.value = '';
element.value = value;
}
});
Demo: http://plnkr.co/edit/pulIrU56qf2J4UjCJlgQ?p=preview

Related

Same AngularJS custom directive on same page

I have a custom search directive and need to use multiple instances of it on the same page. The page makes use of bootstrap tabs and there will be an instance of this search component in each tab.
The issue is that the search directive in the second tab is overriding the callback of the search directive in the first tab. Here is a snippet of my search directive:
class SearchDirective {
constructor($timeout) {
this.require = '^ngModel';
this.restrict= "AE";
this.$timeout = $timeout;
this.scope = {
ngModel: '=',
searchTime: '=',
searchCallback: '&'
};
}
compile(tElem, tAttrs) {
return this.link.bind(this);
}
link(scope, element, attrs) {
this.scope = scope;
var timer = null;
scope.$watch('ngModel', (value, preValue) => {
if (value === preValue) return;
if (timer) {
this.$timeout.cancel(timer);
}
timer = this.$timeout(() => {
timer = null;
if (value.length === 0) {
this.scope.searchCallback();
}
}, this.scope.searchTime)
});
}
}
And here is a snippet of the HTML for the search component on the first tab:
<input search search-callback="searchWindowsController.searchPcs()" search-time="600" data-ng-model="searchWindowsController.searchQuery" type="text" class="searchBox" placeholder="Search Windows...">
And this is what i have in the second tab:
<input search search-callback="searchMacController.searchPcs()" search-time="600" data-ng-model="searchMacController.searchQuery" type="text" class="searchBox" placeholder="Search Macs...">
For some reason when you search using the Windows search, it is calling the Mac callback. Can someone point me to what I am doing wrong? I am new to custom directives.
The error due to this within the $timeout function.
See live example on jsfiddle.
'use strict'
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope, $log) {
$scope.callback1 = function(){
console.log('callback1');
};
$scope.callback2 = function(){
console.log('callback2');
};
})
.directive('search',function($timeout){
return new SearchDirective($timeout);
});
class SearchDirective {
constructor(timeout) {
this.require = '^ngModel';
this.restrict = "AE";
this.$timeout = timeout;
this.scope = {
ngModel: '=',
searchTime: '=',
searchCallback: '&'
};
}
compile(tElem, tAttrs) {
return this.link.bind(this);
}
link(scope, element, attrs) {
this.scope = scope;
var timer = null;
scope.$watch('ngModel', (value, preValue) => {
if (value === preValue) return;
if (timer) {
this.$timeout.cancel(timer);
}
timer = this.$timeout(() => {
timer = null;
if (value.length === 0) {
scope.searchCallback();
}
}, scope.searchTime)
});
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<input search search-callback="callback1()" search-time="600" data-ng-model="searchQuery1" type="text" class="searchBox" placeholder="Search Mac...">
<input search search-callback="callback2()" search-time="600" data-ng-model="searchQuery2" type="text" class="searchBox" placeholder="Search Windows...">
</div>
</div>

How to implement right click with images using angular JS

I am having an example of what i want to achieve given below in plunker.As 10 changes on right click,i want an image there that should convert to some another image on right click.Please help me out with this.
http://jsfiddle.net/bcaudan/vTZZ5/
app.directive('ngRightClick', function($parse) {
return function(scope, element, attrs) {
var fn = $parse(attrs.ngRightClick);
element.bind('contextmenu', function(event) {
scope.$apply(function() {
event.preventDefault();
fn(scope, {$event:event});
});
});
};
});
made this fiddle for you: JsFiddle
Are you looking for something like this??
JS should be like this:
var app = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.img1 = "http://4.bp.blogspot.com/-JOqxgp-ZWe0/U3BtyEQlEiI/AAAAAAAAOfg/Doq6Q2MwIKA/s1600/google-logo-874x288.png";
$scope.img2 = "http://www.socialtalent.co/wp-content/uploads/blog-content/so-logo.png";
$scope.selected = $scope.img1;
$scope.increment = function() {
$scope.selected = $scope.img1;
};
$scope.decrement = function() {
$scope.selected = $scope.img2;
};
};
app.directive('ngRightClick', function($parse) {
return function(scope, element, attrs) {
var fn = $parse(attrs.ngRightClick);
element.bind('contextmenu', function(event) {
scope.$apply(function() {
event.preventDefault();
fn(scope, {$event:event});
});
});
};
});
and HTML should be:
<div ng-app="myApp" ng-controller="MyCtrl">
<span class="action"
ng-click="increment()"
ng-right-click="decrement()"><img ng-src="{{selected}}"></span>
</div>
Please see demo below
var app = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.images = [
'http://upload.wikimedia.org/wikipedia/commons/a/a7/Tiffanie_at_cat_show.jpg',
'http://www.thetimes.co.uk/tto/multimedia/archive/00342/114240651_cat_342943c.jpg'
];
$scope.imageSrc = 1;
$scope.toggleImage = function() {
$scope.imageSrc == 1 ? $scope.imageSrc = 0 : $scope.imageSrc = 1;
};
};
app.directive('ngRightClick', function($parse) {
return function(scope, element, attrs) {
var fn = $parse(attrs.ngRightClick);
element.bind('contextmenu', function(event) {
scope.$apply(function() {
event.preventDefault();
fn(scope, {
$event: event
});
});
});
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<image ng-src="{{images[imageSrc]}}" class="action" ng-right-click="toggleImage()" width="150px" />
</div>

How to find when a specific character is typed in textarea

We have requirement to show a drop down when user enters a "#".
I am planning to have a directive as following:
app.controller('MainCtrl', function($scope) {
$scope.values = ['#'];
$scope.valuesEntered = false;
});
app.directive('identifier', function ($parse) {
return {
scope: {
values: '=values'
},
link: function (scope, elm, attrs) {
elm.bind('keypress', function(e){
var char = String.fromCharCode(e.which||e.charCode||e.keyCode), matches = [];
angular.forEach(scope.values, function(value, key){
if(char === value) matches.push(char);
}, matches);
if(matches.length !== 0){
$scope.valuesEntered = true;
}
});
}
}
});
Will this be ok ?
Here is a simple directive I made that will allow you to specify an expression to evaluate when a given key is pressed or one of an array of keys is pressed.
Note that this is a one-way street. There is currently no going back once you have detected that keypress, even if the user pressed backspace.
var app = angular.module('sample', []);
app.controller('mainCtrl', function($scope) {
$scope.values = ['#', '!'];
$scope.valuesEntered = false;
$scope.valuesEntered2 = false;
});
app.directive('whenKeyPressed', function($parse) {
return {
restrict: 'A',
scope: {
action: '&do'
},
link: function(scope, elm, attrs) {
var charCodesToMatch = [];
attrs.$observe('whenKeyPressed', function(keys) {
if (angular.isArray(keys))
charCodesToMatch = keys.map(function(key) {
if (angular.isString(key))
return key.charCodeAt(0);
});
else if (angular.isString(keys))
charCodesToMatch = keys.split('').map(function(ch) {
return ch.charCodeAt(0);
});
});
elm.bind('keypress', function(e) {
var charCode = e.which || e.charCode || e.keyCode;
if (charCodesToMatch.indexOf(charCode) > -1)
scope.action();
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="sample">
<div ng-controller="mainCtrl">
<p>Values "#" entered? {{valuesEntered}}</p>
<textarea ng-model="str" when-key-pressed="#" do="valuesEntered = true"></textarea>
<p>Values {{values}} entered 2: {{valuesEntered2}}</p>
<textarea ng-model="str2" when-key-pressed="{{values}}" do="valuesEntered2 = true"></textarea>
</div>
</div>
Plunkr demo

Accessing a service or controller in my link function - Angular.js

I have a directive, but I am having a problem access the controller and my service that is injected into it. Here is my directive:
angular.module('clinicalApp').directive('chatContainer', ['encounterService', function(encounterService) {
return {
scope: {
encounter: '=',
count: '='
},
templateUrl: 'views/chat.container.html',
controller: 'EncounterCtrl',
link: function(scope, elem, attrs, controller) {
scope.addMessage = function(message) {
//RIGHT HERE
scope.resetChat();
};
scope.resetChat = function() {
scope.chatText = '';
scope.updateCount(scope.chatText);
};
}
};
}]);
You can see that I am attaching a couple of functions to my scope inside the link function. Inside those methods, like addMessage, I don't have access to my controller or the service that is injected into the directive. How do I acceess the controller or service?
UPDATE
Here is the service:
angular.module('clinicalApp').factory('encounterService', function ($resource, $rootScope) {
var EncounterService = $resource('http://localhost:port/v2/encounters/:encounterId', {encounterId:'#id', port: ':8280'}, {
search: {
method: 'GET'
}
});
var newEncounters = [];
var filterTerms = {};
EncounterService.pushNewEncounter = function(encounter) {
newEncounters.push(encounter);
$rootScope.$broadcast('newEncountersUpdated');
};
EncounterService.getNewEncounters = function() {
return newEncounters;
}
EncounterService.clearNewEncounters = function() {
newEncounters = [];
}
EncounterService.setFilterTerms = function(filterTermsObj) {
filterTerms = filterTermsObj;
$rootScope.$broadcast('filterTermsUpdated');
EncounterService.getFilterTerms(); //filter terms coming in here, must redo the search with them
}
EncounterService.getFilterTerms = function() {
return filterTerms;
}
return EncounterService;
});
and the chat.container.html
<div class="span4 chat-container">
<h5 class="chat-header">
<span class="patient-name-container">{{encounter.patient.firstName }} {{encounter.patient.lastName}}</span>
</h5>
<div class="chat-body">
<div class="message-post-container">
<form accept-charset="UTF-8" action="#" method="POST">
<div class="text-area-container">
<textarea id="chatBox" ng-model="chatText" ng-keyup="updateCount(chatText)" class="chat-box" rows="2"></textarea>
</div>
<div class="counter-container pull-right">
<span class="muted" id="counter">{{count}}</span>
</div>
<div class="button-container btn-group btn-group-chat">
<input id="comment" class="btn btn-primary btn-small btn-comment disabled" value="Comment" ng-click="addMessage(chatText)"/>
</div>
</form>
<div messages-container messages="encounter.comments">
</div>
</div>
</div>
</div>
Here is Demo Plunker I played with.
I removed scope{....} from directive and added 2 values in controller and directive to see how they change regards to action.
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
// listen on any change of chatText in directive
$scope.$watch(function () {return $scope.chatText;},
function (newValue, oldValue) {
if (newValue == oldValue) {return;}
$scope.chatTextFromController = newValue;
}, true);
});
app.directive('chatContainer', ['encounterService', function(encounterService) {
return {
templateUrl: 'chat.container.html',
link: function(scope, elem, attrs) {
scope.countStart = scope.count;
scope.updateCount = function(chatText) {
alert('updateCount');
scope.count = scope.countStart - chatText.length;
};
scope.addMessage = function(message) {
alert('addMessage');
encounterService.sayhello(message);
scope.resetChat();
};
scope.resetChat = function() {
alert('resetChat');
scope.chatText = 'someone reset me';
scope.name = "Hello " + scope.name;
scope.updateCount(scope.chatText);
};
}
};
}]);
app.service('encounterService', function() {
var EncounterService = {};
EncounterService.sayhello = function(message) {
alert("from Service " + message);
};
return EncounterService;
});
HTML
<body ng-controller="MainCtrl">
<div chat-container></div>
<pre>chatText from directive: {{chatText|json}}</pre>
<pre>chatText from controller: {{chatTextFromController|json}}</pre>
<pre>name: {{name|json}}</pre>
</body>

Angular : how to re-render compiled template after model update?

I am working on an angular form builder which generate a json.
Everything works fine except one thing.
You can find an example here : http://jsfiddle.net/dJRS5/8/
HTML :
<div ng-app='app'>
<div class='formBuilderWrapper' id='builderDiv' ng-controller="FormBuilderCtrl" >
<div class='configArea' data-ng-controller="elementDrag">
<h2>drag/drop</h2>
<form name="form" novalidate class='editBloc'>
<div data-ng-repeat="field in fields" class='inputEdit'>
<data-ng-switch on="field.type">
<div class='labelOrder' ng-class='{column : !$last}' drag="$index" dragStyle="columnDrag" drop="$index" dropStyle="columnDrop">{{field.type}}
</div>
<label for="{{field.name}}" data-ng-bind-html-unsafe="field.caption"></label>
<input data-ng-switch-when="Text" type="text" placeholder="{{field.placeholder}}" data-ng-model="field.value" />
<p data-ng-switch-when="Text/paragraph" data-ng-model="field.value" data-ng-bind-html-unsafe="field.paragraph"></p>
<span data-ng-switch-when="Yes/no question">
<p data-ng-bind-html-unsafe="field.yesNoQuestion"></p>
<input type='radio' name="yesNoQuestion" id="yesNoQuestion_yes" value="yesNoQuestion_yes" />
<label for="yesNoQuestion_yes">Oui</label>
<input type='radio' name="yesNoQuestion" id="yesNoQuestion_no" value="yesNoQuestion_no"/>
<label for="yesNoQuestion_no">Non</label>
</span>
<p data-ng-switch-when="Submit button" class='submit' data-ng-model="field.value">
<input value="{{field.name}}" type="submit">
</p>
</data-ng-switch>
</div>
</form>
</div>
<div id='previewArea' data-ng-controller="formWriterCtrl">
<h2>preview</h2>
<div data-ng-repeat="item in fields" content="item" class='templating-html'></div>
</div>
</div>
</div>
The JS :
var app = angular.module('app', []);
app.controller('FormBuilderCtrl', ['$scope', function ($scope){
$scope.fields = [{"type":"Text/paragraph","paragraph":"hello1"},{"type":"Yes/no question","yesNoQuestion":"following items must be hidden","yes":"yes","no":"no"},{"type":"Text/paragraph","paragraph":"hello2"},{"type":"Submit button","name":"last item"}] ;
}]);
app.controller('elementDrag', ["$scope", "$rootScope", function($scope, $rootScope, $compile) {
$rootScope.$on('dropEvent', function(evt, dragged, dropped) {
if($scope.fields[dropped].type == 'submitButton' || $scope.fields[dragged].type == 'submitButton'){
return;
}
var tempElement = $scope.fields[dragged];
$scope.fields[dragged] = $scope.fields[dropped];
$scope.fields[dropped] = tempElement;
$scope.$apply();
});
}]);
app.directive("drag", ["$rootScope", function($rootScope) {
function dragStart(evt, element, dragStyle) {
if(element.hasClass('column')){
element.addClass(dragStyle);
evt.dataTransfer.setData("id", evt.target.id);
evt.dataTransfer.effectAllowed = 'move';
}
};
function dragEnd(evt, element, dragStyle) {
element.removeClass(dragStyle);
};
return {
restrict: 'A',
link: function(scope, element, attrs) {
if(scope.$last === false){
attrs.$set('draggable', 'true');
scope.dragStyle = attrs["dragstyle"];
element.bind('dragstart', function(evt) {
$rootScope.draggedElement = scope[attrs["drag"]];
dragStart(evt, element, scope.dragStyle);
});
element.bind('dragend', function(evt) {
dragEnd(evt, element, scope.dragStyle);
});
}
}
}
}]);
app.directive("drop", ['$rootScope', function($rootScope) {
function dragEnter(evt, element, dropStyle) {
element.addClass(dropStyle);
evt.preventDefault();
};
function dragLeave(evt, element, dropStyle) {
element.removeClass(dropStyle);
};
function dragOver(evt) {
evt.preventDefault();
};
function drop(evt, element, dropStyle) {
evt.preventDefault();
element.removeClass(dropStyle);
};
return {
restrict: 'A',
link: function(scope, element, attrs) {
if(scope.$last === false){
scope.dropStyle = attrs["dropstyle"];
element.bind('dragenter', function(evt) {
dragEnter(evt, element, scope.dropStyle);
});
element.bind('dragleave', function(evt) {
dragLeave(evt, element, scope.dropStyle);
});
element.bind('dragover', dragOver);
element.bind('drop', function(evt) {
drop(evt, element, scope.dropStyle);
var dropData = scope[attrs["drop"]];
$rootScope.$broadcast('dropEvent', $rootScope.draggedElement, dropData);
});
}
}
}
}]);
app.controller('formWriterCtrl', ['$scope', function ($scope){
}]);
app.directive('templatingHtml', function ($compile) {
var previousElement;
var previousIndex;
var i=0;
var inputs = {};
var paragraphTemplate = '<p data-ng-bind-html-unsafe="content.paragraph"></p>';
var noYesQuestionTemplate = '<p data-ng-bind-html-unsafe="content.yesNoQuestion"></p><input id="a__index__yes" type="radio" name="a__index__"><label for="a__index__yes" />{{content.yes}}</label><input id="a__index__no" class="no" type="radio" name="a__index__" /><label for="a__index__no">{{content.no}}</label>';
var submitTemplate = '<p class="submit"><input value="{{content.name}}" type="submit" /></p>';
var getTemplate = function(contentType, contentReplace, contentRequired) {
var template = '';
switch(contentType) {
case 'Text/paragraph':
template = paragraphTemplate;
break;
case 'Yes/no question':
template = noYesQuestionTemplate;
break;
case 'Submit button':
template = submitTemplate;
break;
}
template = template.replace(/__index__/g, i);
return template;
}
var linker = function(scope, element, attrs) {
i++;
elementTemplate = getTemplate(scope.content.type);
element.html(elementTemplate);
if(previousElement == 'Yes/no question'){
element.children().addClass('hidden');
element.children().addClass('noYes'+previousIndex);
}
if(scope.content.type == 'Yes/no question'){
previousElement = scope.content.type;
previousIndex = i;
}
$compile(element.contents())(scope);
}
return {
restrict: "C",
link: linker,
scope:{
content:'='
}
};
});
On the example there are 2 areas :
- the first one does a ngRepeat on Json and allow to reorder items with drag and drop
- the second area also does a ngRepeat, it is a preview templated by a directive using compile function. Some elements are hidden if they are after what I called "Yes/no question"
Here is an example of Json generated by the form builder :
$scope.fields =
[{"type":"Text/paragraph","paragraph":"hello1"},{"type":"Yes/no question","yesNoQuestion":"following items must be hidden","yes":"yes","no":"no"},
{"type":"Text/paragraph","paragraph":"hello2"},{"type":"Submit button","name":"last item"}] ;
When the page load everything is ok, Hello1 is visible and Hello2 is hidden.
But when I drop Hello1 after "Yes/no question", dom elements are reorganised but Hello1 is not hidden.
I think it comes from $compile but I don't know how to resolve it.
Could you help me with this please?
Thank you
I only see you setting the 'hidden' class on the element based on that rule (after a yes/no) in the link function. That's only called once for the DOM element - when it's first created. Updating the data model doesn't re-create the element, it updates it in place. You would need a mechanism that does re-create it if you wanted to do it this way.
I see three ways you can do this:
In your linker function, listen for the same dropEvent that you listen for above. This is more efficient than you'd think (it's very fast) and you can re-evaluate whether to apply this hidden class or not.
Use something like ngIf or literally re-creating it in your collection to force the element to be recreated entirely. This is not as efficient, but sometimes is still desirable for various reasons.
If your use case is actually this simple (if this wasn't a redux of something more complicated you're trying to do) you could use CSS to do something like this. A simple rule like
.yes-no-question + .text-paragraph { display: none; }
using a sibling target could handle this directly without as much work. This is much more limited in what it can do, obviously, but it's the most efficient option if it covers what you need.

Resources