📜 ⬆️ ⬇️

Recreating subdatasheet in table on AngularJS

Hello! I would like to share with you the development of sub-tables for our web project. The goal was to recreate a web module that mimics tables and sub-tables (subdatasheet) created on the basis of Access. Our client is accustomed to working on Access'e, but the times are changing, and now our task is to smoothly switch to the web platform, with minimal difference.


Why AngularJS?


Having not a lot of experience with various javascript libraries, I came to the conclusion that AngularJS initially forces your project to be small, clean, isolated and easily extensible. Also, using directive with its isolated osprey (scope) allows multiple use, even within itself. That will be demonstrated under the cut.


How our final directive will apply


Since there will be many such sub-tables in our project, we need to make our utility convenient in use. It should be something like this:


<div ng-controller="ctrl1"> <subgrid config="config1"> </subgrid> </div> 

Check out the demo here . Who cares please under the cat.



 .controller('ctrl1', function(){ ///... $scope.config1 = { t1:{ subgrid:true, width:300, height:200, config:[ { title:"Filed 1", map:"field1" }, { title:"Filed 2", map:"field2" }, { title:"Filed 3", map:"field3" }, { title:"Filed 4", map:"field4" }, { title:"Filed 5", map:"field5" }, { title:"Filed 6", map:"field6" } ], t:"", load:function(id, idx){ $p.ajax($mock.data1,200).then(function(d){ $scope.config1.t2.init(d); },function(d){ $scope.config1.t2.timeout(d); }); } }, t2: { subgrid:true, width:200, height:100, config:[ { title:"Filed 1", map:"field1" }, { title:"Filed 2", map:"field2" } ], t:"", load:function(id, idx){ $p.ajax($mock.data1,200).then(function(d){ $scope.config1.t3.init(d); },function(d){ $scope.config1.t3.timeout(d); }); } }, t3: { subgrid:false, width:200, height:100, config:[ { title:"Filed 1", map:"field1" }, { title:"Filed 2", map:"field2" } ], t:"" } }; /// --- }); 

It's simple! We set the directives, attach the configuration object to it, and the table with the nest of the sub-tables is ready.


Now about the code itself


Consider our directives:


 .directive('subgrid', ['$timeout','$compile',function($timeout,$compile) { return { restrict: 'E', scope: { config: '=', count: '=' }, templateUrl: 'subgrid.html', link: function(scope, elem, attr, ngModelCtrl) { scope.endrender=function(){ $timeout(function(){ scope.render = false; },1); } scope.expanded = false; scope.expandedid = null; scope.cnt = scope.count?scope.count:1; scope.cnf = scope.config["t"+scope.cnt]; scope.guid = guid(); scope.$watch('cnf.t',function() { scope.render = true; }, true); scope.cnf.timeout = function(error){ scope.cnf.subgrid = false; scope.cnf.config = [{title:"Message",map:"field1"}]; scope.cnf.t = {RowCount:1, field1:[error],index:[1]}; } scope.cnf.init = function(d){ scope.cnf.t = ""; $timeout(function(){ scope.cnf.t = d; },1); } scope.expander = function(id, idx){ //if not same row if(scope.cnf.subgrid) if(id!==scope.expandedid){ angular.element(elem[0].querySelector("#"+scope.expandedid)).children().eq(0).children().text("+"); angular.element(elem[0].querySelector("#"+scope.guid+'sub')).remove(); scope.expandedid = id; var count = scope.cnt + 1; var tr = angular.element(elem[0].querySelector("#"+id)); tr.children().eq(0).children().text("-"); var exid = scope.guid+'sub'; tr.after($compile("<tr id='"+exid+"'><td colspan ='{{cnf.config.length+1}}' style='padding:10px;'><subgrid count='"+count +"' config='config'></subgrid></td></tr>")(scope)); if (typeof scope.cnf.load === "function") { scope.config["t"+count].t = ""; scope.cnf.load(id, idx); } scope.expanded = true; } else{ if(scope.expanded){ angular.element(elem[0].querySelector("#"+id)).children().eq(0).children().text("+"); angular.element(elem[0].querySelector("#"+scope.guid+'sub')).remove(); scope.expanded = false; scope.expandedid = null; } else{ scope.expanded = true; scope.expandedid = id; var count = scope.cnt + 1; var tr = angular.element(elem[0].querySelector("#"+id)); tr.children().eq(0).children().text("-"); var exid = scope.guid+'sub'; tr.after($compile("<tr id='"+exid+"'><td colspan ='{{cnf.config.length+1}}' style='padding:10px;'><subgrid count='"+count +"' config='config'></subgrid></td></tr>")(scope)); if (typeof scope.cnf.load === "function") { scope.config["t"+count].t = ""; scope.cnf.load(id, idx); } } } } function guid() { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return "id"+s4() + s4(); } } }; }]); 

Walk in order in Kratz:


 { restrict: 'E', scope: { config: '=', count: '=' } 

And so, we limit the type of our directive as an element of E. There is no need to make it different, so that there is less confusion.


 config:'=', count: '=' 

need to inject objects from the controller (controller) into certain directives.


 scope.endrender=function(){ $timeout(function(){ scope.render = false; },1); } scope.$watch('cnf.t',function() { scope.render = true; }, true); 

endrender is used to hide the process of building the table itself, since the lateness of requests from distant servers gives ugly spectacular consequences that are easier to replace with a beautiful spiner. scope.render is activated after the last row of the table has been constructed. scope. $ watch listens for every table change and deactivates scope.render before it ends.


 scope.expander = function(id, idx){ //if not same row if(scope.cnf.subgrid) if(id!==scope.expandedid){ angular.element(elem[0].querySelector("#"+scope.expandedid)).children().eq(0).children().text("+"); angular.element(elem[0].querySelector("#"+scope.guid+'sub')).remove(); scope.expandedid = id; var count = scope.cnt + 1; var tr = angular.element(elem[0].querySelector("#"+id)); tr.children().eq(0).children().text("-"); var exid = scope.guid+'sub'; tr.after($compile("<tr id='"+exid+"'><td colspan ='{{cnf.config.length+1}}' style='padding:10px;'><subgrid count='"+count +"' config='config'></subgrid></td></tr>")(scope)); if (typeof scope.cnf.load === "function") { scope.config["t"+count].t = ""; scope.cnf.load(id, idx); } scope.expanded = true; } else{ if(scope.expanded){ angular.element(elem[0].querySelector("#"+id)).children().eq(0).children().text("+"); angular.element(elem[0].querySelector("#"+scope.guid+'sub')).remove(); scope.expanded = false; scope.expandedid = null; } else{ scope.expanded = true; scope.expandedid = id; var count = scope.cnt + 1; var tr = angular.element(elem[0].querySelector("#"+id)); tr.children().eq(0).children().text("-"); var exid = scope.guid+'sub'; tr.after($compile("<tr id='"+exid+"'><td colspan ='{{cnf.config.length+1}}' style='padding:10px;'><subgrid count='"+count +"' config='config'></subgrid></td></tr>")(scope)); if (typeof scope.cnf.load === "function") { scope.config["t"+count].t = ""; scope.cnf.load(id, idx); } } } } function guid() { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return "id"+s4() + s4(); } 

expander is the main handler when scrolling through a table to a subtable. The main feature for generating a new sub-table is to dynamically introduce our own element inside of us. But it is worth noting that for internal use, we add the count attribute. This is in order to distinguish which table from our config1 configuration, directives should use which table t1, t2, etc. .


guid simply simply assigns a unique ID to each row in each table. So that we could confidently change / delete the row we need in the table we need.


Template


 <div class="t-datasheet" ng-class="{'spinner':render}" ng-style="{'width':cnf.width+'px','height':cnf.height+'px'}"> <table ng-hide="render"> <thead > <tr> <td>#</td> <td ng-repeat="c in cnf.config" ng-cloak>{{c.title}}</td> </tr> </thead> <tbody > <tr id="{{guid+i}}" ng-repeat="i in cnf.t.index" ng-init="($last && endrender())"> <td ng-click="expander(guid+i,i)" ><span ng-show="cnf.subgrid">+</span></td> <td ng-repeat="c in cnf.config" ng-cloak>{{cnf.t[c.map][i-1]}}</td> </tr> </tbody> </table> </div> 

The template turned out to be simple and dynamic, where the table has the ability to dynamically expand the columns as config1 is configured.


Everything else that is not described here is the utilities used to test this code (for example: instead of real http requests, I used promises, and set each of them a different timeout).


This project is a prototype of my further development. And I am sharing with the aim of finding out about your opinions and tips on how to improve it.


Demo


')

Source: https://habr.com/ru/post/323092/


All Articles