Autocomplete using AngularJS and Restangular - angularjs

I am trying to add autocomplete using angularJS and restangular.
http://plnkr.co/edit/Ud0c34afYZvHJ6ZQQX9N?p=preview
I am not sure how to add the following in order to make autocomplete work. Could someone suggest how to make this work
angular.module('emps', ['restangular']).directive('autoComplete', function($timeout) {
return function($scope, iElement, iAttrs) {
iElement.autocomplete({
source: $scope[iAttrs.uiItems],
select: function() {
$timeout(function() {
iElement.trigger('input');
}, 0);
}
});
};
});

While there's not alot of info about what you need this for, I think this could be best solved without a separate directive, using the html5 tag.
Using this requires modifying the existing index.html to contain the following.
<div ng-controller="AutoCtrls">
<input list="names" ng-model="selected">
<datalist id="names">
<option value="{{name}}" ng-repeat="name in names"></option>
</datalist>
selected = {{selected}}
</div>
For reference, the original code in index.html was
<div ng-controller='AutoCtrls'>
<input auto-complete ui-items="names" ng-model="selected">
selected = {{selected}}
</div>

Related

How to add conditional add html attribute?

How to add conditionally attribute in angularjs?
For example I only want to set the multiple attribute on a <select> if my component has a binding set to true. This means if the binding is not given the multiple attribute should not exist on my <select>.
The only solution I found was with ng-if.
You can achieve this by implementing a directive (aka ng-multiple) to handle the property multiple of the select element. The following snippet implements this solution. However, you may want to control the state of your model inside this directive, once the multiple prop will produce an array of selected values, the non-multiple will produce a single object, so this may cause an issue when switching between multiple and non-multiple.
angular.module('myApp', [])
.directive('ngMultiple', function () {
return {
require: ['select', '?ngModel'],
restrict: 'A',
scope: {
multiple: '=ngMultiple'
},
link: function (scope, element, attrs, ctrls) {
element.prop('multiple', scope.multiple);
scope.$watch('multiple', function (multiple) {
if(element.prop('multiple') != multiple){
// may be would be convenient change the ngModel
// to [] or {} depending on the scenario
element.prop('multiple', multiple);
}
});
}
};
})
.controller('myController', function ($scope) {
$scope.condition = true;
$scope.myOptions = [];
});
angular.element(document).ready(function () {
angular.bootstrap(document, ['myApp']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-controller="myController">
<label>
<input type="checkbox" ng-model="condition" /> multiple?
</label>
<br>
<select ng-multiple="condition" ng-model="myOptions">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
<option>Option 4</option>
<option>Option 5</option>
</select>
<br>
<tt>myOptions: {{ myOptions }}</tt>
</div>
if boolean condition is true then multiple, else not
<select ng-if="condition" ng-model="some.model" multiple></select>
<select ng-if="!condition" ng-model="some.model"></select>
<select ng-show="condition" ng-model="some.model" multiple></select>
<select ng-hide="condition" ng-model="some.model"></select>
controller,
$scope.condition = true

Angular JS Directive loaded in expression

I'm new to Angular, and have found a ton of resources about directives and nesting, but can't seem to get this simple example to work. So basically I am working on a tabset, I have an HTML template:
tabset.html
<div>
<ul>
<li ng-repeat="tab in tabset.tabs" ng-class="{active:tabset.current()==$index}">
<a href ng-click="tabset.current($index)">{{tab}}</a>
</li>
</ul>
<div>
<div ng-repeat="pane in tabset.panes">
<div ng-show="tabset.current()==$index">
{{pane.contents}}
</div>
</div>
</div>
</div>
And a search form template:
search-form.html
<div>
<form name="ytSearch" ng-submit="YTCtrl.submit()" novalidate>
<label for="search_box">Search For: </label>
<input id="search_box" ng-model="YTCtrl.searchString"/>
<br>
<label for="location">Location: </label>
<input id="location" ng-model="YTCtrl.location"/>
within
<input type="numeric" value="100" ng-model="YTCtrl.locationRadius" />
<select ng-model="YTCtrl.locationUnit">
<option value="ft">Feet</option>
<option value="m">Meters</option>
<option value="mi">Miles</option>
<option value="km">Kilometers</option>
</select>
<br>
<label for="search_order">Sort By: </label>
<select id="search_order" ng-model="YTCtrl.order">
<option value="relevance">Relevance</option>
<option value="date">Date</option>
<option value="rating">Rating</option>
</select>
<br>
<button id="search">
Search
</button>
</form>
</div>
And a simple app file with 2 directives to handle each of the templates:
app.js
(function() {
angular.module("JHYT", [])
.directive("sidebarTabset", function($compile) {
return {
restrict : 'E',
templateUrl : 'tabset.html',
controller : function($scope, $compile, $http) {
this._current = 0;
this.current = function(i) {
if (i != null)
this._current = i;
return this._current;
};
this.tabs = ['Search', 'Favorite'];
this.panes = [{
contents : "<search-form></search-form>"
}, {
contents : "Favorite Pane"
}];
},
controllerAs : 'tabset',
};
}).
directive("searchForm", function() {
return {
restrict : 'E',
templateUrl : 'search-form.html',
controller : function($scope, $compile, $http) {
this.searchString = '';
this.location = '';
this.locationRadius = '';
this.locationUnit = 'mi';
this.order = 'relevance';
this.submit = function() {
console.log("Submit");
};
},
controllerAs : 'YTCtrl',
}
});
})();
So as you can probably tell, the idea is to be able to send a JSON object into the tabset (through a service probably) and have it build out a dynamic tabset, that actually works exactly as I expected it to. What isn't working is that in the first tab, the content, which is <search-form></search-form> is not processed, and the tag is rendered as plain text in the content area.
Since this is a tabset, the "child" doesn't need anything from the "parent", the search form and the tab itself have no scope dependencies. I tried playing with the link and compile functions after seeing some examples of nested structures, but can't seem tog et them to work.
How can I process the content of that variable so that element directives are rendered using their templates?
EDIT:
#sielakos Gave me exactly what I was hoping for, a reusable method for doing this.
I added a directive to my module called compile, which adds a wrapper to allow me to use plain text:
.directive("compile", function($compile){
return {
restrict: 'A',
link: function(scope, element, attr){
attr.$observe("compile", function(str){
var compiled = $compile("<div>"+str+"</div>")(scope);
jQuery(element).replaceWith(compiled);
})
}
}
})
And I changed my tabset to use this directive:
<div>
<ul>
<li ng-repeat="tab in tabset.tabs" ng-class="{active:tabset.current()==$index}">
<a href ng-click="tabset.current($index)">{{tab}}</a>
</li>
</ul>
<div>
<div ng-repeat="pane in tabset.panes">
<div ng-show="tabset.current()==$index">
<div compile="{{pane.contents}}"></div>
</div>
</div>
</div>
</div>
You will need to compile your string using $compile service if you wish to use it as you would use template. Otherwise it will be treated as normal string and displayed as it is.
Here is example how to use it inside directive:
var compiled = $compile(str)(scope);
element.empty();
element.append(compiled);
If you wish you can look at this fiddle for more complex example:
https://jsfiddle.net/x78uuwp2/
Here I created simple compile directive that takes string compiles it and puts as element body with current scope.

Set angularjs input directive name as a variable

I'm trying to set an input like
<form name="myForm">
<input name="{{ name }}"/>
</form>
It works in the dom. I see
<input name="whatever_name_is_set_to"/>
However in my ngForm I have
$scope.myForm: {
{{ name }}: { }
}
Doh'
Why am I doing this? I'm trying to create a directive so that I can build my forms programmatically. Then I can do something like
<div my-field
name="credits"
field="course.credits"
field-options="courseOptions.credits"
title="Credits"
></div>
Plunker
Updated 2017-03-23:
For AngularJS >= 1.3 interpolation is now supported in input names.
Original answer from 2014-06-05 (correct for AngularJS <= 1.2):
I just answered a similar question yesterday about dynamically named form elements. In short, no, you can't use interpolation to dynamically name your form fields - the interpolation string will end up in the field name as you have seen.
In your case you're probably going to need to look into dynamically compiling the input HTML inside your myField directive.
Here is a simplified example using $compile to dynamically generate form elements: http://jsfiddle.net/Sly_cardinal/XKYJ3/
HTML:
<div ng-app="myApp">
<form name="myForm" ng-controller="myController">
<div my-field
name="courseName"
field="course.courseName"
title="Course Name"></div>
<div my-field
name="credits"
field="course.credits"
title="Credits"></div>
<!-- Show that the values are bound. -->
<pre>course: {{course | json:' '}}</pre>
<!-- Show that the field is being registered with the ngFormController. -->
<pre>myForm.credits.$dirty: {{myForm.credits.$dirty}}</pre>
</form>
</div>
JavaScript:
angular.module('myApp', [])
.controller('myController', ['$scope', function($scope){
$scope.course = {
credits: 100,
courseName: 'Programming 201'
};
}])
.directive('myField', ['$compile', '$parse', function($compile, $parse){
// In a real project you'd probably want to use '$templateCache' instead
// of having strings in your code.
var tmpl = $compile('<label>{{title}}</label>');
return {
scope: true,
link: function(scope, element, attr){
scope.title = attr.title;
var newEl = angular.element('<input type="text"/>');
newEl.attr('ng-model', attr.field);
newEl.attr('name', attr.name);
tmpl(scope, function(fieldEl, scope){
$compile(newEl[0].outerHTML)(scope, function(el, scope){
fieldEl.append(el);
element.append(fieldEl);
});
});
}
}
}]);
A note on this example:
This is a very specific situation - generating dynamic form elements - that requires the use of $compile. This is not the "go to" solution when working with Angular inputs and forms - Angular will handle all the common situations with directives, data-binding and everything else the framework provides. Plus, as Marc Kline's comment shows, it looks like at some point Angular will handle dynamic form management itself at some point in the future.
If you were to continue down the path using $compile to generate these form elements then you'd probably want to use the $templateCache to manage your templates so you're not trying to manage template strings inside your directive.
Old question, but in case someone is looking for a way to do what question asked, you can create a directive that will dynamically create the name of the element after $compile'ing it.
An updated version of the answer that #Sly_cardinal posted is here: http://jsfiddle.net/XKYJ3/1/
HTML
<div ng-app="myApp">
<form name="myForm" ng-controller="myController">
<label for="{{ course.courseName.name }}" ng-bind="course.courseName.title"></label>
<input id="{{ course.courseName.name }}" dynamic-input-name="course.courseName.name" ng-model="course.courseName.value" type="text" required />
<br />
<label for="{{ course.credits.name }}" ng-bind="course.credits.title"></label>
<input id="{{ course.credits.name }}" dynamic-input-name="course.credits.name" ng-model="course.credits.value" type="number" required />
<!-- Show that the values are bound. -->
<pre>course: {{course | json:' '}}</pre>
<!-- Show that the field is being registered with the ngFormController. -->
<pre>myForm.credits_field.$dirty: {{ myForm.credits_field.$dirty }}</pre>
</form>
</div>
Javascript
angular.module('myApp', [])
.controller('myController', ['$scope', function($scope){
$scope.course = {
credits: {
title: 'Credits',
value: 100,
name: 'credits_field'
},
courseName: {
title: 'Course name',
value: 'Programming 201',
name: 'course_name_field'
}
};
}])
.directive('dynamicInputName', ['$compile', '$parse', function($compile, $parse){
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-input-name'))(scope);
elem.removeAttr('dynamic-input-name');
elem.attr('name', name);
$compile(elem)(scope);
}
};
}]);

Angularjs directive - select next element by a class name

I have the following HTML:
<div>
<input type="text" ng-model="search.q" special-input>
<ul class="hidden">...</ul>
<ul class="hidden bonus">...</ul>
</div>
And the following directive:
myApp.directive('specialInput', ['$timeout', function($timeout)
{
return {
link: function(scope, element) {
element.bind('focus', function() {
$timeout(function() {
// Select ul with class bonus
element.parent().find('.bonus').removeClass('hidden');
});
});
}
}
}]);
I want to select the ul.bonus using jqLite but cannot find a way. I tried with .next(".bonus") but the selector is ignored completely and the first ul is selected. Does anyone have an idea why I can't do this?
P.S. I'm just relying on AngularJS internal jqLite without jQuery.
Thanks!
you will not need a directive to achieve this:
<div>
<input type="text"
ng-model="search.q"
ng-focus="bonus=true"
ng-blur="bonus=false">
<ul class="hidden">...</ul>
<ul ng-show="bonus">...</ul>
</div>
if your needs are more complex put the decision about the bonus state in your controller.

Dynamic validation and name in a form with AngularJS

I have this form : http://jsfiddle.net/dfJeN/
As you can see the name value for the input is statically set :
name="username"
, the form validation works fine (add something and remove all text from the input, a text must appears).
Then I try to dynamically set the name value : http://jsfiddle.net/jNWB8/
name="{input.name}"
Then I apply this to my validation
login.{{input.name}}.$error.required
(this pattern will be used in an ng-repeat) but my form validation is broken. It is correctly interpreted in my browser (if I inspect the element I saw login.username.$error.required).
Any Idea ?
EDIT: After logging the scope in the console it appears that the
{{input.name}}
expression is not interpolate. My form as an {{input.name}} attribute but no username.
UPDATE: Since 1.3.0-rc.3 name="{{input.name}}" works as expected. Please see #1404
You can't do what you're trying to do that way.
Assuming what you're trying to do is you need to dynamically add elements to a form, with something like an ng-repeat, you need to use nested ng-form to allow validation of those individual items:
<form name="outerForm">
<div ng-repeat="item in items">
<ng-form name="innerForm">
<input type="text" name="foo" ng-model="item.foo" />
<span ng-show="innerForm.foo.$error.required">required</span>
</ng-form>
</div>
<input type="submit" ng-disabled="outerForm.$invalid" />
</form>
Sadly, it's just not a well-documented feature of Angular.
Using nested ngForm allows you to access the specific InputController from within the HTML template. However, if you wish to access it from another controller it does not help.
e.g.
<script>
function OuterController($scope) {
$scope.inputName = 'dynamicName';
$scope.doStuff = function() {
console.log($scope.formName.dynamicName); // undefined
console.log($scope.formName.staticName); // InputController
}
}
</script>
<div controller='OuterController'>
<form name='myForm'>
<input name='{{ inputName }}' />
<input name='staticName' />
</form>
<a ng-click='doStuff()'>Click</a>
</div>
I use this directive to help solve the problem:
angular.module('test').directive('dynamicName', function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-name'))(scope);
// $interpolate() will support things like 'skill'+skill.id where parse will not
elem.removeAttr('dynamic-name');
elem.attr('name', name);
$compile(elem)(scope);
}
};
});
Now you use dynamic names wherever is needed just the 'dynamic-name' attribute instead of the 'name' attribute.
e.g.
<script>
function OuterController($scope) {
$scope.inputName = 'dynamicName';
$scope.doStuff = function() {
console.log($scope.formName.dynamicName); // InputController
console.log($scope.formName.staticName); // InputController
}
}
</script>
<div controller='OuterController'>
<form name='myForm'>
<input dynamic-name='inputName' />
<input name='staticName' />
</form>
<a ng-click='doStuff()'>Click</a>
</div>
The problem should be fixed in AngularJS 1.3, according to this discussion on Github.
Meanwhile, here's a temporary solution created by #caitp and #Thinkscape:
// Workaround for bug #1404
// https://github.com/angular/angular.js/issues/1404
// Source: http://plnkr.co/edit/hSMzWC?p=preview
app.config(['$provide', function($provide) {
$provide.decorator('ngModelDirective', function($delegate) {
var ngModel = $delegate[0], controller = ngModel.controller;
ngModel.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('name', $interpolate(attrs.name || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}];
return $delegate;
});
$provide.decorator('formDirective', function($delegate) {
var form = $delegate[0], controller = form.controller;
form.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('name', $interpolate(attrs.name || attrs.ngForm || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}];
return $delegate;
});
}]);
Demo on JSFiddle.
Nice one by #EnISeeK.... but i got it to be more elegant and less obtrusive to other directives:
.directive("dynamicName",[function(){
return {
restrict:"A",
require: ['ngModel', '^form'],
link:function(scope,element,attrs,ctrls){
ctrls[0].$name = scope.$eval(attrs.dynamicName) || attrs.dynamicName;
ctrls[1].$addControl(ctrls[0]);
}
};
}])
Just a little improvement over EnlSeek solution
angular.module('test').directive('dynamicName', ["$parse", function($parse) {
return {
restrict: 'A',
priority: 10000,
controller : ["$scope", "$element", "$attrs",
function($scope, $element, $attrs){
var name = $parse($attrs.dynamicName)($scope);
delete($attrs['dynamicName']);
$element.removeAttr('data-dynamic-name');
$element.removeAttr('dynamic-name');
$attrs.$set("name", name);
}]
};
}]);
Here is a plunker trial. Here is detailed explantion
I expand the #caitp and #Thinkscape solution a bit, to allow dynamically created nested ng-forms, like this:
<div ng-controller="ctrl">
<ng-form name="form">
<input type="text" ng-model="static" name="static"/>
<div ng-repeat="df in dynamicForms">
<ng-form name="form{{df.id}}">
<input type="text" ng-model="df.sub" name="sub"/>
<div>Dirty: <span ng-bind="form{{df.id}}.$dirty"></span></div>
</ng-form>
</div>
<div><button ng-click="consoleLog()">Console Log</button></div>
<div>Dirty: <span ng-bind="form.$dirty"></span></div>
</ng-form>
</div>
Here is my demo on JSFiddle.
I used Ben Lesh's solution and it works well for me. But one problem I faced was that when I added an inner form using ng-form, all of the form states e.g. form.$valid, form.$error etc became undefined if I was using the ng-submit directive.
So if I had this for example:
<form novalidate ng-submit="saveRecord()" name="outerForm">
<!--parts of the outer form-->
<ng-form name="inner-form">
<input name="someInput">
</ng-form>
<button type="submit">Submit</button>
</form>
And in the my controller:
$scope.saveRecord = function() {
outerForm.$valid // this is undefined
}
So I had to go back to using a regular click event for submitting the form in which case it's necessary to pass the form object:
<form novalidate name="outerForm"> <!--remove the ng-submit directive-->
<!--parts of the outer form-->
<ng-form name="inner-form">
<input name="someInput">
</ng-form>
<button type="submit" ng-click="saveRecord(outerForm)">Submit</button>
</form>
And the revised controller method:
$scope.saveRecord = function(outerForm) {
outerForm.$valid // this works
}
I'm not quite sure why this is but hopefully it helps someone.
This issue has been fixed in Angular 1.3+
This is the correct syntax for what you are trying to do:
login[input.name].$invalid
if we set dynamic name for a input like the below
<input name="{{dynamicInputName}}" />
then we have use set validation for dynamic name like the below code.
<div ng-messages="login.dynamicInputName.$error">
<div ng-message="required">
</div>
</div>

Resources