AngularJS Table Column Search...smarter ideas? - angularjs

I have to use AngularJS to build a dashboard and one of the components is a table.
Since I did not find relevant dependencies/libraries for angularjs (like tabulator or datatables), I am doing it myself.
Instead of using the native angular filter, I built a custom method, but I am not sure if I am following a good approach.
The main idea is that when I pull the data object (array of objects) via Ajax, I create both an "original" and a "current" data object,s and at the beginning, they are exactly the same of course.
Then I created an input field above every column heading and I linked the search function to the blur and keyup events (enter key).
When the search function is triggered, I start making changes to the "current" object. This way I can filter by multiple columns incrementally. I filter the data object using an awesome library called AlaSQL.
I also linked to a button the "reset" method, which simply makes the "current" object equal to the "original" object, and cleans up the input fields.
The point is, am I missing any best practices? Are there better ways to do so with AngularJS?
Any suggestions?
Thanks a lot.
HTML
<div ng-app="myApp">
<div ng-controller="divController">
<my-table></my-table>
</div>
</div>
JS
var app = angular.module('myApp', []);
app.controller('divController', function ($scope, $http) {
$scope.data = {};
$scope.data.current = null;
$scope.data.original = null;
$scope.filter = {
id: {
field: "id",
value: null
},
name: {
field: "name",
value: null
},
owner: {
field: "owner",
value: null
},
}
$scope.reset = function () {
console.log("reset");
$scope.data.current = $scope.data.original;
for (let prop in $scope.filter) {
$scope.filter[prop]["value"] = null;
}
}
$scope.filterExec = function (field, value) {
if (value) {
console.log(`Executing filter on field "${field.trim()}" by this value "${value.trim()}"`);
var filtered = alasql('SELECT * FROM ? where ' + field + ' LIKE "%' + value + '%"', [$scope.data.current]);
$scope.data.current = filtered;
}
}
$http.get("./workspaces_demo_obj.json")
.then(function (response) {
console.log(response);
$scope.data.original = response.data;
$scope.data.current = response.data;
});
});
app.directive('myTable', function () {
return {
template:
'<div>Total rows {{data.current.length}} <button ng-click="reset()">RESET</button></div>' +
'<table class="table table-responsive table-sm">' +
'<thead>' +
'<tr><th>Workspace ID</th>' +
'<th>Name</th>' +
'<th>Owner</th></tr>' +
'<tr><th><input ng-model="filter.id.value" ng-blur="filterExec(filter.id.field, filter.id.value)" ng-keydown="$event.keyCode === 13 && filterExec(filter.id.field, filter.id.value)" placeholder="Filter by id"></input></th>' +
'<th><input ng-model="filter.name.value" ng-blur="filterExec(filter.name.field, filter.name.value)" ng-keydown="$event.keyCode === 13 && filterExec(filter.name.field, filter.name.value)" placeholder="Filter by name"></input></th>' +
'<th><input ng-model="filter.owner.value" ng-blur="filterExec(filter.owner.field, filter.owner.value)" ng-keydown="$event.keyCode === 13 && filterExec(filter.owner.field, filter.owner.value)" placeholder="Filter by owner"></input></th></tr>' +
'</thead>' +
'<tbody>' +
'<tr ng-repeat="x in data.current">' +
'<td>{{ x.workspace_id }}</td>' +
'<td>{{ x.name }}</td>' +
'<td>{{ x.owner }}</td>' +
'</tr>' +
'</tbody>' +
' </table>',
restrict: 'E'
};
});

Related

AngularJS - Using custom filter in ng-repeat for prefixing comma

Need to remove comma if value is empty works good if I have value
present at start or middle; But same doesn't work in this scenario.
app.filter('isCSV', function() {
return function(data) {
return (data !== '') ? data + ', ' : '';
};
});
Angularjs ng repeat for addressline - Plunker
I would instead operate on arrays of properties and use a pair of filters, one to remove empty values, and one to join the array.
This way it's very explicit about what properties you are displaying.
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in details">
{{ [ item.address0, item.address1, item.address2, item.address3] | removeEmpties | joinBy:', ' }}
</li>
</ul>
</body>
With the following filters:
app.filter('removeEmpties', function () {
return function (input,delimiter) {
return (input || []).filter(function (i) { return !!i; });
};
});
app.filter('joinBy', function () {
return function (input,delimiter) {
return (input || []).join(delimiter || ',');
};
});
Here's the updated Plunkr
Tricky but should work in your case Also no filter need
{{ item.address0 }} <span ng-if="item.address1">,
</span> {{ item.address1}}<span ng-if="item.address2">,</span>{{
item.address2}}
<span ng-if="item.address3">,</span>{{ item.address3}}
Here is working example
I would prefer writing a function instead of adding a filter so many times.
$scope.mergeAddresses = function(item) {
var address = item.address0;
[1,2,3].forEach(function(i) {
var add = item["address"+i];
if (!add) return;
address += (address ? ", " : "") + add;
});
if (address) address += ".";
return address;
}
Plunker

How to access item data from dxList ,Devextreme Mobile

HTML
<div data-bind="dxList: { dataSource: dataSource }">
<div data-options="dxTemplate : { name: 'item' } " >
<div class="list-item" data-bind="text: name"></div>
<div data-bind="dxCheckBox: {value: check }"></div>
</div>
</div>
<div data-bind="text: 'Save', click: save"></div>
Javascript
var dataSource = ko.observableArray([]);
dataSource.push({ name: "name1", check: true });
dataSource.push({ name: "name2", check: false });
save: function () {
}
How to get 'name' and 'check' values inside save function,Devextreme mobile?
You just can use the dataSource array.
var save = function () {
var items = dataSource();
console.log(items[0].name + " - " + items[0].check());
console.log(items[1].name + " - " + items[1].check());
};
If you want to get only checked items, you can use the jQuery.grep function to filter data.
var items = $.grep(dataSource(), function(item){
return item.check() === true;
});
Also, I suggest you use the ko.observable() to define the check field of items. It allows you to track changes of the check field.
http://jsfiddle.net/d4t1pqby/3/

Ionic collection-repeat with date dividers

I got a very large list of about 200 items with text and images. ng-repeat is way to slow to render this smoothly. It tried it with this solution. Works nice. But not with collection-repeat.
My web-service return this:
There are events with specific dates. The events should be grouped by date. So in order to use collection repeat, how is it possible to insert dividers, if you cant use angular.filter groupBy?
I can offer you a partial solution which would only work if the dataset is ordered by the displayed field in the divider.
First of all we need to create a fake element in the array so that we can discriminate the divider amongst the other element.
Let's say we have a collection of posts fetched from a webservice:
.controller('mainController', function($scope, dataService) {
$scope.posts = [];
var divider = '';
});
the private field divider will be in use when we load the posts.
And we will have the loadMore method to load extra data when we scroll the list:
$scope.loadMore = function(argument) {
page++;
dataService.GetPosts(page, pageSize)
.then(function(result) {
if (result.data.length > 0) {
angular.forEach(result.data, function(value, key) {
value.divider = false;
if (value.postId !== divider)
{
divider = value.postId;
$scope.posts.push({divider: true, dividerText: value.postId});
}
$scope.posts.push(value);
});
}
else {
$scope.theEnd = true;
}
})
.finally(function() {
$scope.$broadcast("scroll.infiniteScrollComplete");
});
};
When we fetch the data from the web api (and the promise is resolved) we loop through the collection and check if the field is different from the divider. If this is a new divider we store the info and add a new element to the collection:
angular.forEach(result.data, function(value, key) {
value.divider = false;
if (value.postId !== divider)
{
divider = value.postId;
$scope.posts.push({divider: true, dividerText: value.postId});
}
$scope.posts.push(value);
});
As you can see I've added an element:
$scope.posts.push({divider: true, dividerText: value.postId});
I've used a dividerText field which will be displayed later on.
Now we need to create our own directive divider-collection-repeat which should be attached to a collection repeat:
<ion-item collection-repeat="post in posts" item-height="75" divider-collection-repeat>
I guess you're using infinite-scroll, so here is the whole HTML:
<ion-content ng-controller="mainController">
<ion-list>
<ion-item collection-repeat="post in posts" item-height="75" divider-collection-repeat>
{{post.name}}
</ion-item>
</ion-list>
<ion-infinite-scroll ng-if="!theEnd" on-infinite="loadMore()" distance="50%"></ion-infinite-scroll>
</ion-content>
this is the directive:
.directive('dividerCollectionRepeat', function($parse) {
return {
priority: 1001,
compile: compile
};
function compile (element, attr) {
var height = attr.itemHeight || '75';
var itemExpr = attr.collectionRepeat.split(' ').shift();
attr.$set('itemHeight', itemExpr + '.divider ? 40 : (' + height + ')');
attr.$set('ng-class', itemExpr + '.divider ? "item-divider" : ""');
var children = element.children().attr('ng-hide', itemExpr + '.divider');
element.prepend(
'<div ng-show="' + itemExpr + '.divider" class="my-divider" ' +
'ng-bind="' + itemExpr + '.dividerText" style="height:100%;">' +
'</div>'
);
return function postLink(scope, element, attr) {
scope.$watch(itemExpr + '.divider', function(divider) {
element.toggleClass('item-divider', !!divider);
});
};
}
});
The directive prepends an element (html) to the list using the expression you've defined in your collection-repeat.
In my sample I've use collection-repeat="post in posts" so this line:
var itemExpr = attr.collectionRepeat.split(' ').shift();
fetches the item's name; in my case it is going to be post.
We use the height as well cause we might need to have a different height for the divider.
This bit here is the place where all the magic happens:
element.prepend(
'<div ng-show="' + itemExpr + '.divider" class="my-divider" ' +
'ng-bind="' + itemExpr + '.dividerText" style="height:100%;">' +
'</div>'
);
It uses an ng-show for the field 'post.divider' (ng-show="' + itemExpr + '.divider") and binds the our text field ng-bind="' + itemExpr + '.dividerText"
I've also added a custom class my-divider just in case we need to change the layout of our divider a bit.
The final result is here or in this plunker.
As you might have noticed I haven't used a date field as I already had a sample where, sadly, I didn't have any dates.
I guess it should be very easy to adapt to your situation.
The directive is based on a sample I have found on github.
You will find the code for the directive here.

Bootstrap Select drop list moves down ward automatically

I am Bootstrap Select user in IE11.
When I fill data with ajax and open it then it automatically move down wards and when I select any value then it's not selected. How can I remove this error?
jQuery.ajax({
url: base_url + "UserBusinesses/ajaxState/" + countryId,
dataType: 'json',
beforeSend: function () {
$('#UserBusinessStateId').html('<option value="">Loding... </option>');
$('#UserBusinessStateId').selectpicker('refresh');
$('#UserBusinessCityId').html('<option value="">Select city</option>');
$('#UserBusinessCityId').selectpicker('refresh');
},
success: function (obj) {
var addHtml = '';
addHtml += '<option value="">Select state</option>';
$.each(obj, function (key, value) {
var selected = "";
if (selectedState == key) {
var selected = "selected='selected'";
}
addHtml += "<option value='" + key + "' " + selected + ">" + value + "</option>";
});
jQuery("#UserBusinessStateId").html(addHtml);
$('#UserBusinessStateId').selectpicker('refresh');
}
});
I am facing this issue In IE and I have find the solution
$('#UserBusinessStateId').selectpicker('refresh');
remove this and add this code
$('#UserBusinessStateId').selectpicker('render').selectpicker('refresh');

Angular Select2 formatResult not updating the template

I have a select2 drop down with the following markup:
<select id="selectByName" ui-select2="select2Options" ng-model="selectId" data-placeholder="Select item by name" style="width:250px">
<option></option>
<option ng-repeat='item in items' data-show="{{item.show}}" value="{{item.id}}">
{{item.name}}
</option>
</select>
And the js contains the following:
$scope.items (an array that has a id, a boolean show property and a name property)
and the select 2 options:
select2Options : {
allowClear: true,
placeholder:"select a value",
formatResult: function(state) {
var $elem = angular.element(state.element),
isVisible = $elem.data('show');
return isVisible ? '<span style="color:red">'+state.text+'</span>':
<span style="color:blue">'+state.text+'</span>';
}
},
Well, the ng-repeat updates correctly the html markup and sets data-show attribute to either true or false, but the formatResult function does not update this value.
In the html source the data-show="true" and in the formatResult function $elem.data('show') = false;, why doesn't it update while the function is called every time the select2 is opened?
Here is made a plunker that illustrates my question: plnkr.co/edit/d0LxuhzdQh7hMdzOoxpr?p=preview .It looks the formatResult updates the results correctly only once before opening the select2 for the first time.
Edit
http://plnkr.co/edit/6Vma1WTQWQw0HAIQUVxE?p=preview
$scope.select2options = {
allowClear: true,
placeholder: "select a value",
formatResult: function(state, container) {
var $elem = angular.element(state.element);
var scope = $elem.data('$scope');
if (scope !== undefined) {
isVisible = scope.$eval($elem.data('show'));
$scope.dataShow[$elem.attr('value')] = isVisible;
$scope.updated++;
return isVisible ? '<span style="color:red">' + state.text + '</span>' :
' <span style="color:blue">' + state.text + '</span>'
}
}
}
The key part is grabbing the $scope data from the jqLite element and then calling $eval, which evaluates an unparsed string expression in the context of the scope. If we had used $scope.$eval, it would have used the controller $scope, which wouldn't have the ng-repeat on it. By grabbing it from the element we have a scope that has access to the item property for the ng-repeat.
Having said that I don't recommend using this code (sometimes jQuery widgets force you into unpleasant corners when working with angular). Again if you find yourself manipulating angular.element or using $element in a controller you probably should use a directive instead. Then again we programmers have to deal with non-ideal constraints (time, money, etc.) that prevent us from working "ideally" so given your context this may be a decent solution.
Let me know if any of my explanation doesn't make sense.
Original
http://plnkr.co/edit/vYTdxPwgwqZSgK5m9yk9?p=preview
Is this what you want?
JavaScript
$scope.items = [{
id: 1,
show: false,
name: 'test1'
}, {
id: 2,
show: true,
name: 'test2'
}, {
id: 3,
show: true,
name: 'test3'
}];
$scope.selections = [1, 2];
$scope.getStyleForIndex = function (index) {
var item;
for (var i = 0; i < $scope.items.length; i++) {
if (i === index) {
item = $scope.items[i];
break;
}
}
return item.show ? { color: "red" } : { color: "blue" };
}
$scope.select2options = {
allowClear: true,
formatResult: function(item, container) {
var color = $scope.getStyleForIndex(parseInt(item.id, 10)).color;
container.html('<span style="color:' + color + '">RESULT ' + item.text + '</span>');
},
formatSelection: function(item, container) {
container.append($compile('<span ng-style="getStyleForIndex(' + item.id + ')">SELECTION ' + item.text + '</span>')($scope));
}
}
HTML
<div ng-repeat="item in items">
{{ item.name }}
<input type="checkbox" ng-model="item.show" />
</div>
<select ui-select2="select2options" ng-model="selections" style="width:200px" multiple="true" ng-options="i.id as i.name for i in items"></select>
{{selections}}

Resources