My directive for ng-focus.
Edit: I was told there is existing core ng-focus. as i am very new to angular I don't see ng-focus as an option. As in fig below. Also it does not work
var ngControlMod = angular.module('ngControlModule', []);
ngControlMod.directive('ngFocus', function ($timeout) {
return {
link: function (scope, element, attrs) {
scope.$watch(attrs.ngFocus, function (val) {
if (angular.isDefined(val) && val) {
$timeout(function () { element[0].focus(); });
}
}, true);
element.bind('blur', function () {
if (angular.isDefined(attrs.ngFocusLost)) {
scope.$apply(attrs.ngFocusLost);
}
});
}
};
});
Here is how I am using in in my view
<table id="tblMyTable" class="table table-bordered table-striped table-hover table-condensed">
<thead>
<tr>
<th>My Dropdown</th>
<th>My Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="ca in myList">
<td>
<select name="select_{{ca.ID}}" ng-model="ca.List"
ng-options="corr as corr.caText for corr in caList track by corr.caValue"
ng-change="GetData(ca, ca.List)">
<option value="">--Select--</option>
</select>
</td>
<td>
<input name="Date_{{ca.ID}}" ng-focus="isFocus_{{ca.ID}}"
date-picker="" ng-model="ca.caDate"
placeholder="MM/DD/YYYY" />
</td>
</tr>
</tbody>
</table>
The idea is, if a user selects an option in dropdown the focus should automatically move to date in that row.
To achieve this I have added an ID as an identifier in attribute "name" for the html control in table row and in my controller
$scope.GetData= function (ca, Selected) {
var propName = "isFocus_" + ca.ID;
$scope[propName] = true;
}
After all the coding the result is not consistent. it works sometimes and other times it does . And I can't figure out the reason. Also I am getting this error
angular.js:14700 Error: [$parse:syntax] Syntax Error: Token '{' is an
unexpected token at column 9 of the expression [isFocus_{{ca.QuesID}}]
starting at [{{ca.QuesID}}].
http://errors.angularjs.org/1.6.6/$parse/syntax?p0=%7B&p1=is%20an%20unexpected%20token&p2=9&p3=isFocus_%7B%7Bca.QuesID%7D%7D&p4=%7B%7Bca.QuesID%7D%7D
at angular.js:116
at AST.throwError (angular.js:15258)
at AST.ast (angular.js:15008)
at Parser.parse (angular.js:16350)
at $parse (angular.js:16496)
at Object.compile (angular.js:27337)
at applyDirectivesToNode (angular.js:9737)
at compileNodes (angular.js:9097)
at compileNodes (angular.js:9109)
at compileNodes (angular.js:9109) "<input name="Date_{{ca.ID}}" ng-focus="isFocus_{{ca.ID}}" date-picker="" ng-model="ca.caDate" placeholder="MM/DD/YYYY" />"
Be aware that adding a custom ng-focus directive will not replace the existing core ng-focus. The $compile service will instantiate both the new custom directive and the existing core ng-focus directive.
Avoid using the ng- prefix for custom directives. The ng- prefix is reserved for core directives. For more information, see AngularJS Wiki - Best Practices.
<input name="Date_{{ca.ID}}" ̶n̶g̶-̶f̶o̶c̶u̶s̶=̶"̶i̶s̶F̶o̶c̶u̶s̶_̶{̶{̶c̶a̶.̶I̶D̶}̶}̶"̶
ng-focus="'isFocus_'+ca.ID"
date-picker="" ng-model="ca.caDate"
placeholder="MM/DD/YYYY" />
Why mixing interpolation and expressions is bad practice:
It increases the complexity of the markup
There is no guarantee that it works for every directive, because interpolation itself is a directive.
If another directive accesses attribute data before interpolation has run, it will get the raw interpolation markup and not data.
It impacts performance, as interpolation adds another watcher to the scope.
Since this is not recommended usage, we do not test for this, and changes to AngularJS core may break your code.
— AngularJS Developer Guide - Interpolation
Related
I'm relatively new to angular and this is my first involved directive and I'm a little lost. I'm trying to create a re-usable list of items on separate tabs in my app. The list acts the same with the exception of how the items are displayed (handled by separate partials). I'm passing in attributes of many different types into the scope and I'm trying several different things based on what I've been reading. No matter what I've tried so far, I'm still having issues with the attributes binding correctly.
Below is my code and I'll try to explain it as best as possible, hopefully someone can tell me where I went wrong. The only things that appear to have bound correctly are the strings, the objects and functions are missing.
UPDATE: Turns out I needed to bind $scope.currentPage to the directive scope. Now ng-repeat is running, but other parts of the page that require access to the controller scope aren't working. I've updated the code below and continue looking into how to give access to the template.
Directive
var app = angular.module('main');
app.directive('itemList', function(){
var linkFunction = function(scope, element, attributes){
scope.$watch("query.value", function(){
scope.filterFunction(); //pretty sure this never gets called on search
});
}
return {
restrict: 'E',
replace: 'true',
templateUrl: 'partials/directives/list-tab.html',
link: linkFunction,
scope: {
filterFunction: "&filterFunction",
searchPlaceholder: "#searchPlaceholder",
pagedItems: "=pagedItems",
clickFunction: "&clickFunction",
classString: "#classString",
infoTemplate: "#template",
currentPage: "=currentPage"
}
}
});
index.html
//pagedCars is an array of nested objects that gets used by the template to display the information
//filterCars is a function
//carSelected is a function
<div class="available-items">
<item-list filter-function="filterCars" search-placeholder="Search Cars" paged-items="pagedCars" current-page="currentPage" click-function="carSelected" class-string="car.carId==selectedCar.carId?'selected':''" template="'partials/cars/cars-template.html'"></item-list>
</div>
list-tab.html
<div class="form-group">
<div class="search-field">
<label for="searchField" id="searchLabel">Search</label><br/>
<input type="text" ng-model="query.value" placeholder="{{searchPlaceholder}}"/>
</div>
<table class="table table-hover>
<tbody>
//currentPage is on the controller scope there's a separate control that allows the user to page through the pagedItems by updating the currentPage which would be reflected here
<tr ng-repeat="item in pagedItems[currentPage]" ng-click="clickFunction($index) ng-class="classString">
<td ng-include="infoTemplate"></td>
</tr>
</tbody>
</table>
</div>
cars-template.html
<div class="row form-inline" id="{{item.carId}}">
<div class="col-md-2">
//this uses a method on the controller scope to format the url
<img ng-src="{{retrieveIcon(item.iconUrl)}}" height="75px" width="75px"/>
<div class="col-md-10">
<div details-pane" id="carDetails" ng-include="'partials/cars/car-full-details.html'"></div>
<div class="item-title">{{item.name}}</div>
//the rest is just a table with more information about the item. item.description, item.mileage, etc...
</div>
</div>
Try passing through your functions with their parentheses
<div class="available-items">
<item-list filter-function="filterCars()" search-placeholder="Search Cars" paged-items="pagedCars" click-function="carSelected()" class-string="car.carId==selectedCar.carId?'selected':''" template="'partials/cars/cars-template.html'"></item-list>
</div>
Also FYI if your variable has the same name in your HTML as you want in your directive's scope you can just use the pass method. e.g.
scope: {
filterFunction: "&",
searchPlaceholder: "#",
pagedItems: "#",
clickFunction: "&",
classString: "#",
infoTemplate: "#template"
}
I have a table set up using the smart-table plug in for AngularJS. Everything appears to work nicely. Rather than having the user click on the table header to trigger a sort, I'd like to programmatically trigger sorting from my Angular controller. I do not see a way of doing this in the documentation here:
http://lorenzofox3.github.io/smart-table-website/
Am I overlooking something?
Found this on JSFiddle, might help you: http://jsfiddle.net/vojtajina/js64b/14/
<script type="text/javascript" ng:autobind
src="http://code.angularjs.org/0.10.5/angular-0.10.5.js"></script>
<table ng:controller="SortableTableCtrl">
<thead>
<tr>
<th ng:repeat="(i,th) in head" ng:class="selectedCls(i)" ng:click="changeSorting(i)">{{th}}</th>
</tr>
</thead>
<tbody>
<tr ng:repeat="row in body.$orderBy(sort.column, sort.descending)">
<td>{{row.a}}</td>
<td>{{row.b}}</td>
<td>{{row.c}}</td>
</tr>
</tbody>
</table>
A quick hack i found on how to do this is by setting the table header st-sort property and then triggering a click() on that element
<tr>
<th id="myelement" st-sort="date" st-sort-default="reverse">Date</th>
...
</tr>
Then later:
setTimeout(function() {
document.getElementById('myelement').click()
},
0);
Here is the 'angular' way to do this. Write a directive. This directive will have access to the smart table controller. It will be able to call the controller's sort by function. We will name the new directive stSortBy.
The below HTML includes the standard smart table syntatic sugar. The only new attribute directive here is st-sort-by. That's where the magic will happen. It's bound to a scoped variable sortByColumn. This is a string value of the column to sort
<table st-sort-by="{{sortByColumn"}}" st-table="displayedCollection" st-safe-src="rowCollection">
<thead>
<tr>
<th st-sort="column1">Person</th>
<th st-sort="column2">Person</th>
</tr>
</thead>
</table>
<button ng-click="toggleSort()">Toggle sort columns!</button>
Here is the stSortBy directive that hooks into the st table controller
app.directive('stSortBy', function() {
return {
require: 'stTable',
link: function (scope, element, attr, ctrl) {
attr.$observe('stSortBy', function (newValue) {
if(newValue) {
// ctrl is the smart table controller
// the second parameter is for the sort order
ctrl.sortBy(newValue, true);
}
});
}
};
});
Here is the controller. The controller sets the sort by in it's scope
app.controller("MyTableWrapperCtrl", ["$scope", function($scope) {
$scope.sortByColumn = 'column2';
$scope.toggleSort = function() {
$scope.sortByColumn = $scope.sortByColumn === 'column2' ? 'column1' : 'column2';
// The time out is here to guarantee the attribute selector in the directive
// fires. This is useful is you do a programmatic sort and then the user sorts
// and you want to programmatically sort back to the same column. This forces a sort, even if you are sorting the same column twice.
$timeout(function(){
$scope.sortByColumn = undefined;
}, 0);
};
}]);
I am working along DB guys, they are sending me the data thru XML, and depending the kind of element they specify is what I need to display in the view.
The code you will see is a dynamic table
<table>
<thead>
<tr>
<th ng-repeat="column in cols">
<span>{{column}}</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in cols"
ng-init="isXX = column.indexOf('XX') === 0">
<span ng-if="!isXX">{{row[column]}}</span>
<button ng-if="isXX" class="btn btn-xs btn-blue"
ng-click="fillOpen()">
{{column.substring(3).replace('_', ' ')}}
</button>
</td>
</tr>
</tbody>
</table>
and here is what I have in the controller
ReportsFactory.pendingBets(reportParam).then(function(data) {
if (data.length) {
gridInfo = _.forEach(data, function(item) {return item;});
$scope.rows = gridInfo;
$scope.cols = Object.keys($scope.rows[0]);
}
}
as you can see here I have this ng-init
ng-init="isXX = column.indexOf('XX') === 0" where I am telling the app, if the property I am receiving comes with XX at the index, then display a button <button ng-if="isXX" ng-click="fillOpen()">...</button> but so far, I have some more props coming with XX at the beginning, so I need to do it more dynamic.
This is how my view looks so far
what I need to know, is how to read that XML, this is the XML printed in the Nodejs terminal
[{ BET: 57635034,
CUSTOMER: 181645,
SPORT: 'NFL',
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>',
XX_VIEW: '<element><element_type>BASIC_DROPDOWN</element_type><element_call>callThisFunction()</element_call><element_content><li>1</li><li>2</li><li>3</li><li>4</li></element_content></element>',
XX_CANCEL: '<element><element_type>BASIC_CHECKBOX</element_type><element_call/><element_content>1</element_content></element>'
}]
so, the first says
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>'
WAGER_ACTION_BUTTON should be a button
the second one says
BASIC_DROPDOWN that should be a dropdown and so on, so, how should I do in order to display the proper HTML element depending on what the XML says ?
Any suggestions ?
if I understood you correctly you want to dynamically render the xml or html content to your view... I assume that element and element type are directive you have or something.
use
ngBindHtml
e.g:
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="question.question.body"></span>
</div>
or you might need to use the trustAsHtml function
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="trustAsHtml(question.question.body)"></span>
</div>
$scope.trustAsHtml = function (val) {
return $sce.trustAsHtml(val);
};
this will take your string xml (html) code and render it...
you could always build a personalize directive and use $compile as well like:
app.directive('ngHtmlCompile',function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.ngHtmlCompile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
and in the code just call the ng-html-compile... no need for $sce
I have a table which I iterate throurh rows and show information for editing. There I have a file input for image uploading. File input is a bootstrap extension which provides a nice interface. Here is the link : http://plugins.krajee.com/file-input/demo
It has a simple usage you can define your input component using attributs in input tag like data-show-upload or you can use jquery to initialize the component, for example:
$("#photo").fileinput({showCaption: false});
Here is my table :
<table id="table-brand" class="table" style="border-style:none">
<tr>
<td><b>Marka Adı</b></td>
<td><b>Bilgi</b></td>
<td><b>Aktif</b></td>
<td><b>Logo</b></td>
</tr>
<tr name="brand" ng-repeat="brand in selectedCustomer.brands">
<td><input name="name" type="text" class="form-control"
id="field-brand-name"
style="width:150px" value="" title="Vergi No"/></td>
<td><textarea name="bio" type="text" class="form-control"
id="field-brand-bio"
style="width:150px" value="" title="Vergi No"></textarea></td>
<td><input index="0" id="photo0" name="photo" type="file" class="file"
accept=".jpg,.jpeg,.png" style="width:120px;"
data-show-upload="false" data-show-caption="false"
data-show-preview="false"/></td>
</tr>
</table>
The issue is when I use it with ng-repeat, the file input does not work(The javascript code bind to it doesn't work). When i removed ng-repeat then it works. I guess it's because we need to initialize those inputs using javascript like above because angular creates these elements. So tried the lines :
$(document).ready(function() {
$('[name="photo"]').fileinput({showCaption: false, showPreview: false, showUpload: false});
});
But it didn't work. Also without deleting ng-repeat, if i take input element outside '< td >' it works.
Thanks.
Edit : I debugged the line
$(document).ready(function() { .. }
and when code execution stops there, angular has not yet rendered the page so query returns nothing and no initializer executes.
I need some function that tells angular to execute after rendering the page or ng-repeat.
use this
$(document).ready(function() {
k();
});
var k= function { $('#photo0').fileinput({showCaption: false, showPreview: false, showUpload: false});}
Use a custom directive to run the jQuery code. Put the directive on the input field and run the manupilation in the link function of directive.
angular.module('moduleName')
.directive('myDirective', function () {
return {
restrict: 'EA', //E = element, A = attribute, C = class, M = comment
scope: {
//# reads the attribute value, = provides two-way binding, & works with functions
title: '#' },
template: '<div>{{ myVal }}</div>',
templateUrl: 'mytemplate.html',
controller: controllerFunction, //Embed a custom controller in the directive
link: function ($scope, element, attrs) { } //DOM manipulation
}
});
You only need the lunk function from above which gives you the directive element
Try to create on the controller a function to call the fileinput on the init of the input and pass the input id, like this
<input ng-init="myCtrl.init_input_file('your_file_input')" id="your_file_input" type="file">
And then creates the function with the fileinput on yout Js
$my.init_input_file = function(id){
$timeout(function(){
$("#"+id).fileinput();
$('#'+id).change(function(){
$my.models.files[id] = this.files;
});
});
};
try to create a place to allocate the file inputs on change before you use it.
I solve the problem with the ng-repeat with a timeout without the time, to wait the HTML render.
Simple html:
<table class="table table-condensed">
<tr data-ng-repeat="customer in customers" data-ng-class="customerSelectedClass(customer)">
<td>
{{customer.Name}}
</td>
</tr>
</table>
In my controller - two functions to select customer and return proper class to highlight a table row:
$scope.customerSelectedClass = function (customer) {
if (customer == $scope.selectedCustomer) {
console.log('returing info for ' + customer.Name);
return "info";
}
return "";
};
$scope.selectCustomer = function (customer) {
console.log('selecting ' + customer.Name);
$scope.selectedCustomer = customer;
}
I noticed that when I click on a customer link, customerSelectedClass function executes twice. selectCustomer function on ng-click directive executes once, as it should. Angular is only included once on the page. I wonder if this is a bug in Angular or something that I am doing wrong?
Behind the scenes, angular is setting up a $watch on the function that is resolving the class name. Because angular uses dirty checking to see if there has been a change, this method will be called twice during the $digest cycle. This is ok.
I would suggest that you don't add this code the the controller though, because if you are managing many css classes, you could be adding a lot of unnecessary code. Try something like this instead:
<table class="table table-condensed">
<tr data-ng-repeat="customer in customers" data-ng-class="{'info': customer == selectedCustomer}">
<td>
{{customer.Name}}
</td>
</tr>
</table>
Then, there is no need for a controller function customerSelectedClass. This will only add the info class if the right-hand side of the : resolves to true. And there is no problem resolving the correct customer in the ng-repeat.
Hope this helps.