Ag-Grid header Component adding custom header templates - angularjs

I'm still using Angular 1.x and ag-Grid (non-enterprise).
But I'm trying to add tooltips from angular ui-bootstrap. So I need to add an attribute to all headers: "uib-tooltip='headerValue' "
The issue is that it wants me to implement a whole new component. But the example component they put on the ag-grid website is super complicated and different from the default functionality. Why isn't there an easy way of doing these things?
Why deprecate headerTemplate?
So even if I want a slight change from the default headers, I basically need to take over responsibility for everything. And I don't even have the original version of header component as example code.
I just want to add some new HTML to the header that doesn't involve taking responsibility for sorting, field, menu etc.

I know this is old but...I was in this exact situation, where I was wondering why it wasn't provided... I went ahead and built my own which wasn't extremely hard. Since I think someone might find a default header component useful: here's one that is essentially the default
Note you'll need font awesome for the icons and make sure to use the custom component in your column options.
function customHeaderComponent() {
}
customHeaderComponent.prototype.init = function (agParams) {
this.agParams = agParams;
this.eGui = document.createElement('div');
this.eGui.innerHTML = '' +
'<span class="customHeaderLabel">' + this.agParams.displayName + '</span> ' +
'<span class="customSortDownLabel inactive"><i class="fa fa-caret-down""></i> </span>' +
'<span class="customSortUpLabel inactive"><i class="fa fa-caret-up""></i> </span>'+
'<span class="customFilterLabel inactive"><i class="fa fa-filter"></i> </span>'+
'<span class="customHeaderMenuButton"><i class="fa fa-tasks""></i></span>'
;
this.eMenuButton = this.eGui.querySelector(".customHeaderMenuButton");
this.eLabel = this.eGui.querySelector(".customHeaderLabel");
this.eSortDownButton = this.eGui.querySelector(".customSortDownLabel");
this.eSortUpButton = this.eGui.querySelector(".customSortUpLabel");
this.eFilterInd = this.eGui.querySelector(".customFilterLabel");
if (this.agParams.enableMenu) {
this.onMenuClickListener = this.onMenuClick.bind(this);
this.eMenuButton.addEventListener('click', this.onMenuClickListener);
} else {
this.eGui.removeChild(this.eMenuButton);
}
if (this.agParams.enableSorting) {
this.eLabel.addEventListener('click', this.changeSort.bind(this));
this.onSortChangedListener = this.onSortChanged.bind(this);
this.agParams.column.addEventListener('sortChanged', this.onSortChangedListener);
this.onSortChanged();
}
var self = this;
self.eFilterInd.hidden = !self.agParams.column.isFilterActive();
this.agParams.column.addEventListener('filterChanged', function() {
self.eFilterInd.hidden = !self.agParams.column.isFilterActive();
});
};
customHeaderComponent.prototype.changeSort = function (event) {
this.agParams.progressSort(event);
}
customHeaderComponent.prototype.onSortChanged = function () {
function deactivate(toDeactivateItems) {
toDeactivateItems.forEach(function (toDeactivate) {
toDeactivate.hidden = true;
});
}
function activate(toActivate) {
toActivate.hidden = false;
}
if (this.agParams.column.isSortAscending()) {
deactivate([this.eSortUpButton]);
activate(this.eSortDownButton)
} else if (this.agParams.column.isSortDescending()) {
deactivate([this.eSortDownButton]);
activate(this.eSortUpButton)
} else {
deactivate([this.eSortUpButton, this.eSortDownButton]);
}
};
customHeaderComponent.prototype.getGui = function () {
return this.eGui;
};
customHeaderComponent.prototype.onMenuClick = function () {
this.agParams.showColumnMenu(this.eMenuButton);
};
customHeaderComponent.prototype.onSortRequested = function (order, event) {
this.agParams.setSort(order, event.shiftKey);
};
customHeaderComponent.prototype.destroy = function () {
if (this.onMenuClickListener) {
this.eMenuButton.removeEventListener('click', this.onMenuClickListener)
}
};

Related

Autocomplete off on Angular Directive for Date Picker

I have a directive for JQuery Date picker which injects date picker into input HTML control. This was developed by a previous developer and I am pretty new to Angular at this moment.
My question is that is there any way to prevent showing auto complete on all the date pickers that we inject via this directive?
export class DanialDatePickerDirective implements ControlValueAccessor {
constructor(protected el: ElementRef, private renderer: Renderer) { }
#Input() dateformat: string = "DD-MMM-YY";
#Input() ngModel: any;
#Input() setDefaultDate: boolean;
onModelChange: Function = () => { };
onModelTouched: Function = () => { };
writeValue(value: any) {
if (value) {
var ff = new Date(value);
$(this.el.nativeElement).datepicker("setDate", ff);
}
else {
$(this.el.nativeElement).datepicker("setDate", "");
}
}
registerOnChange(fn: Function): void {
this.onModelChange = fn;
}
registerOnTouched(fn: Function): void {
this.onModelTouched = fn;
}
onBlur() {
this.onModelTouched();
}
ngAfterViewInit() {
var self = this;
$(this.el.nativeElement).datepicker({
dateFormat: 'dd-M-y',
changeMonth: true,
changeYear: true,
showOtherMonths: true,
selectOtherMonths: true
});
if (this.setDefaultDate) {
var ff = new Date(self.ngModel);
setTimeout(function () {
$(self.el.nativeElement).datepicker("setDate", ff);
}, 200);
}
$(this.el.nativeElement).on('change', (e: any) => {
var model = e.target.value;
var date = null;
var monthstring = '';
if (model.indexOf("-") > 0){
monthstring = model.substring(model.indexOf("-") + 1, 5);
}
if (isNaN(parseInt(monthstring))) {
var tt = moment(model, "DD-MMM-YY").format('YYYY-MM-DD');
date = tt;
model = moment(model, "DD-MMM-YYYY").format('MM-DD-YYYY')
}
else {
date = moment(model, "DD-MM-YYYY").format('YYYY-MM-DD');
model = moment(model, "DD-MM-YYYY").format('MM-DD-YYYY')
}
$(".ui-datepicker a").removeAttr("href");
self.onModelChange(date);
self.writeValue(date.toString());
});
}
}
The only approach who works for me:
First, make sure to set autocomplete="off" on both, the input element itself and the parent form.
Second, make sure to assign an unique name to your input field always.
This can be achieved by simply generating a random number and using this number in the name of the field.
private getUniqueName() {
return Math.floor(Math.random() * Date.now());
}
Explanation:
In the past, many developers would add autocomplete="off" to their
form fields to prevent the browser from performing any kind of
autocomplete functionality. While Chrome will still respect this tag
for autocomplete data, it will not respect it for autofill data.
https://developers.google.com/web/updates/2015/06/checkout-faster-with-autofill.
So autocomplete="off" solves the autocomplete issue. But to solve the autofill you need to play dirty with the browser by changing the name of the input over an over again, that way the browser will never know how to autofill ;)

Adding glyphicon icons to form fields

I'm wanting to add icons to form fields like bootstrap has: http://getbootstrap.com/css/?#forms-control-validation
I was able to get the class to display properly on the form-group by adjusting the options:
successClass: 'has-success',
errorClass: 'has-error',
classHandler: function (_el) {
return _el.$element.closest('.form-group');
}
but i'm unable to figure out the best way to add the error or checkmark glyphicon. I assume it may have something to do with the errorWrapper / errorContainer but there isn't one for successWrapper/container
I ended up coming up with something else:
var bootstrapParsleyOptions = {
successClass: 'has-success has-feedback',
errorClass: 'has-error has-feedback',
classHandler: function (_el) {
return _el.$element.closest('.form-group');
}
};
$.extend(true, ParsleyUI, {
enableBootstrap: function () {
$(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
window.Parsley.on('form:init', function () {
$(this.$element).find(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
});
window.Parsley.on('field:validated', function () {
var element = this.$element;
if (this.validationResult == true) {
$(element).siblings(".form-control-feedback").removeClass('glyphicon-remove').addClass('glyphicon-ok');
$(element).siblings(".sr-only").text("(success)");
} else {
$(element).siblings(".form-control-feedback").removeClass('glyphicon-ok').addClass('glyphicon-remove');
$(element).siblings(".sr-only").text("(error)");
}
});
},
clearBootstrap: function () {
$(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
}
});
To enable it:
$("#form").parsley(bootstrapParsleyOptions);
ParsleyUI.enableBootstrap();
To reset it:
$("#form").parsley(bootstrapParsleyOptions).reset();
ParsleyUI.enableBootstrap();
I imagine that you can obtain what you want with CSS, something like
.parsley-success::before { content: '√'; }

Intermittent "element is not found" in protractor

In Protractor, I am trying to write a function to simulate clicking a responsive navigation menu item (i.e. menu is either a bar of dropdowns across the top or a hamburger link if mobile). The structure of the bar is defined by MEANJS with a few id="" attributes mixed in.
It seems to work exactly as I want when I run it one spec at a time, like so:
protractor app/tests/e2e/conf.js --suite mySuite
but when I run with the full test (that only has 4 tests in it), like so:
protractor app/tests/e2e/conf.js
I start to get this error intermittently (source of error is in 2 places below):
Failed: element not visible
Here's my function
commonPOs.clickNavBar = function(mainTab, linkUrl) {
var deferred = protractor.promise.defer();
var hamburger = element(by.id('nav-hamburger'));
var linkCssExpression = 'a[href*="' + linkUrl + '"]';
hamburger.isDisplayed().then(function(result) {
if ( result ) {
hamburger.click().then(function() {
var navBar = hamburger
.element(by.xpath('../..'))
.element(by.id('myapp-navbar'));
return clickItNow(mainTab, linkUrl, navBar);
});
} else {
return clickItNow(mainTab, linkUrl, element(by.id('myapp-navbar')));
}
});
return deferred.promise;
function clickItNow(mainTab, linkUrl, navBar) {
var link;
if(mainTab) {
// if mainTab was passed, need to
// click the parent first to expose the link
var parentLink;
if (mainTab == 'ACCTADMIN') {
parentLink = navBar.element(by.id('account-admin-menu'));
}
else {
parentLink = navBar.element(by.linkText(mainTab));
}
expect(parentLink.isPresent()).toBeTruthy();
parentLink.click(); // FIRST PLACE ERROR HAPPENS
link = parentLink.element(by.xpath('..')).element(by.css(linkCssExpression));
}
else {
link = navBar.element(by.css(linkCssExpression));
}
expect(link.isPresent()).toBeTruthy();
link.click(); // SECOND PLACE ERROR HAPPENS
return deferred.fulfill();
}
};
I have tried to use both of these but neither work:
browser.sleep(500);
browser.driver.wait(protractor.until.elementIsVisible(parentLink));
What NOOB async error am I making?
Self answer... but any better answer will win the green check mark!!!
EDITED - After more problems, found a nice 'waitReady()' contribution from elgalu.
There were a few obvious problems with my original code, but fixing them did not solve the problem. After working through what I could, the solution seemed to be the expect(navBar.waitReady()).toBeTruthy(); lines I added. I also had to split the nested function out. Anyway, here is the new code that seems to be working now. A better (comprehensive) answer will get the green checkmark! I'm pretty sure there's a flaw or 2 here.
commonPOs.clickNavBar = function(mainTab, linkUrl) {
var deferred = protractor.promise.defer();
var hamburger = element(by.id('nav-hamburger'));
hamburger.isDisplayed().then(function(result) {
if ( result ) {
hamburger.click().then(function() {
var navBar = hamburger
.element(by.xpath('../..'))
.element(by.id('myapp-navbar'));
return clickItNow(mainTab, linkUrl, navBar, deferred);
});
} else {
return clickItNow(mainTab, linkUrl, element(by.id('myapp-navbar')), deferred);
}
});
return deferred.promise;
};
function clickItNow(mainTab, linkUrl, navBar, deferred) {
var targetLink;
var linkCssExpression = 'a[href*="' + linkUrl + '"]';
expect(navBar.waitReady()).toBeTruthy();
if(mainTab) {
// if mainTab was passed, neet to
// click the parent first to expose the link
var parentTabLink;
if (mainTab == 'ACCTADMIN') {
parentTabLink = navBar.element(by.id('account-admin-menu'));
}
else {
parentTabLink = navBar.element(by.id('main-menu-' + mainTab));
}
// expect(parentTabLink.isDisplayed()).toBeTruthy();
expect(parentTabLink.waitReady()).toBeTruthy();
parentTabLink.click();
targetLink = parentTabLink.element(by.xpath('..')).element(by.css(linkCssExpression));
}
else {
targetLink = navBar.element(by.css(linkCssExpression));
}
expect(targetLink.isDisplayed()).toBeTruthy();
targetLink.click().then(function() {
return deferred.fulfill();
});
}

binding to service variable, doesnt refresh (service changes the var frequently)

In my Service i have the vars i want to display and the getters for it:
var docsLoaded = 0;
var docsToLoad = null;
pouchService.getDocsLoaded = function () {
return docsLoaded;
};
pouchService.getDocsToLoad = function () {
return docsToLoad;
};
While the service is syncing, i want to count the synced docs
pouchService.syncNow = function () {
var foundLastSeq = false;
docsLoaded = 0;
docsToLoad = null;
remoteDB.info().then(function (remoteInfo) {
function findOutDiff(localPosition) {
docsToLoad = (remoteInfo.update_seq - localPosition) + 1;
console.log("docs to load: " + docsToLoad);
}
// start Sync progress
sync = localDB.sync(remoteDB, {live: false})
.on('change', function (info) {
console.log('AI change: ');
console.log(info);
if (info.direction === 'pull') {
if (foundLastSeq === false) {
foundLastSeq = true;
findOutDiff(info.change.last_seq);
}
}
console.log(docsLoaded + " from " + docsToLoad);
docsLoaded++;
})
In my HTML i want to display the progress like this:
{{pouchService.getDocsLoaded()}} from {{pouchService.getDocsToLoad()}}
Now i get sometimes a value from getDocsLoaded, but mostly its zero. When I cancel the Syncprogress i get the value where it's stopped.
So i get the value before it really starts and when it's over, but i want it during the sync progress. (on the console my my progressinfos are working as expected)
Any ideas?
The problem is in applying scope. Jim wrote a nice article about this problem:
jimhoskins.com/2012/12/17/angularjs-and-apply.html
Solved it:
$rootScope.$apply(function () {
docsLoaded++;
});

Why is that the UI is not refreshed when data is updated in typescript and angularjs program

I learn typescript and angularjs for a few days,and now I have a question that confuses me for days, I want to make a gps tracking system, so I try to write a service like this:
1.
module Services {
export class MyService {
getGpsPeople(): Array<AppCommon.GPSPerson> {
var gpsPeople = new Array<AppCommon.GPSPerson>()
for (var i = 0; i < 10; i++) {
var tempPerson = new AppCommon.GPSPerson({ name: "username" + i.toString() });
gpsPeople.push(tempPerson);
}
return gpsPeople;
}
} // MyService class
}
A controller like this:
module AppCommon {
export class Controller {
scope: ng.IScope;
constructor($scope: ng.IScope) {
this.scope = $scope;
}
}
}
module Controllers {
export interface IMyScope extends ng.IScope {
gpsPeople: Array<AppCommon.GPSPerson>;
}
export class MyController extends AppCommon.Controller {
scope: IMyScope;
static $inject = ['$scope','myService'];
constructor($scope: IMyScope,service:Services.MyService) {
super($scope);
$scope.gpsPeople = service.getGpsPeople();
}
}
}
3.The GPSPerson class like this:
export class GPSPoint {
latitude = 0;
longtitude = 0;
constructor(la: number, lg: number) {
this.latitude = la;
this.longtitude = lg;
}
}
export interface IPerson {
name: string;
}
export class GPSPerson
{
name: string;
lastLocation: GPSPoint;
countFlag = 1;
historyLocations: Array<GPSPoint>;
timerToken: number;
startTracking() {
this.timerToken = setInterval(
() => {
var newGpsPoint = null;
var offside = Math.random();
if (this.countFlag % 2 == 0) {
newGpsPoint = new GPSPoint(this.lastLocation.latitude - offside, this.lastLocation.longtitude - offside);
}
else {
newGpsPoint = new GPSPoint(this.lastLocation.latitude + offside, this.lastLocation.longtitude + offside);
}
this.lastLocation = newGpsPoint;
this.historyLocations.push(newGpsPoint);
console.log(this.countFlag.toString() + "+++++++++++++++++++" + this.lastLocation.latitude.toString() + "----" + this.lastLocation.longtitude.toString());
this.countFlag++;
}
, 10000);
}
stopTracking() {
clearTimeout(this.timerToken);
}
constructor(data: IPerson) {
this.name = data.name;
this.lastLocation = new GPSPoint(123.2, 118.49);
this.historyLocations = new Array<GPSPoint>();
}
}
The problem is:
1.Should I make the GPSPerson class a Controller?
2.The setinterval works but the UI dose not change(when I hit button ,it changes,the button do nothing )?
I'm a beginner of ts and angular,and have no experience with js, I do not know if I have explained it clearly, hope someone can help me, thanks!
setInterval works but the ui dose not change
This is because the angular $digest loop does not run on completion of setInterval. You should use $interval service https://docs.angularjs.org/api/ng/service/$interval as that tells Angular to do its dirty checking again.
You just need to provide MyService access to $interval though. Inject it using $inject (see https://www.youtube.com/watch?v=Yis8m3BdnEM&hd=1).
1.should i make the GPSPerson class a Controller?
No. Its an array of JavaScript objects inside the controller and that is fine.
I would separate the tracking logic and keep just the data in GPSPerson. For the tracking logic I would make a factory.
My examples are not in Typescript but I'm sure you will have no problem in converting the code if you want.
This is a link to a Plunk
I've made a much simpler example but I think you'll understand the idea.
The factory would have two methods for start and stop tracking. They will take a person as parameter.
app.factory('tracking',function($interval){
var trackingInterval;
var trackingFn = function(person){
var currentPos = Math.floor(Math.random()*10);
var newPosition = {id:person.positions.length, position:currentPos};
person.positions.push(newPosition);
};
var startTracking = function(person){
person.interval = $interval(function(){
trackingFn(person);
},2000);
};
var stopTracking = function(person){
console.log('STOP');
$interval.cancel(person.interval);
};
var getNewTrack = function(){
};
return {
startTracking: startTracking,
stopTracking: stopTracking,
};
});
I've also made a very simple directive to show the data
app.directive('position',function(){
return {
templateUrl: 'positionTemplate.html',
link: function(scope, element,attrs){
}
}
});
and the template look like this
<div>
<button ng-click="startTracking(person)">Start tracking</button>
<button ng-click="stopTracking(person)">Stop tracking</button>
<p>{{person.name}}</p>
<ul>
<li ng-repeat="pos in person.positions">{{pos.position}}</li>
</ul>
</div>
And the directive would be called this way
<div ng-repeat="person in people">
<div position></div>
</div>
I'm not saying that this is a better solution but that is how I would do it. Remember it is just a model and needs a lot of improvement.

Resources