angular.module('myapp', [])
.controller('ctrl1', ['$scope', '$http','$p','$mock', function($scope, $http, $p, $mock) {
$scope.init = function() {
console.clear();
$p.ajax($mock.data1,1000).then(function(d){
$scope.config1.t1.init(d);
},function(d){
$scope.config1.t1.timeout(d);
});
$p.ajax($mock.data2,500).then(function(d){
$scope.config2.t1.init(d);
});
}
$scope.refreshPage = function(){
$scope.init();
}
$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:""
}
};
$scope.config2 = {
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.eajax($mock.data1,200).then(function(d){
$scope.config2.t2.init(d);
},function(d){
$scope.config2.t2.timeout(d);
});
}
},
t2: {
subgrid:false,
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);
});
}
}
};
}]);
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<script src="controller.js"></script>
<script src="subgrid.js"></script>
</head>
<body ng-app="myapp">
<div ng-controller="ctrl1" ng-init="init()">
<subgrid config="config1"> </subgrid>
<hr>
<subgrid config="config2"> </subgrid>
<button ng-click="refreshPage()">refresh</button>
</div>
</body>
</html>
angular.module('myapp')
.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();
}
}
};
}])
.service('$mock',[function() {
this.data1 = {
RowCount:5,
field1:["raz","dva","tri","cht","pyat"],
field2:[1,2,3,4,5],
field3:["sh","sem","vos","dev","des"],
field4:[4,5,4,1,4],
field5:[true,false,true,true,false],
field6:["sdfdsf","sdfdsfdsf","rtyjyjtyj","wrtwrt","wrtw"],
index:[1,2,3,4,5]
};
this.data2 = {
RowCount:5,
field1:["sd","dfg","iopu","ndw","bfj"],
field2:[1,2,3,4,5],
field3:["btr","ekhg","fs","tmvc","soigb"],
field4:[4,5,4,1,4],
field5:[true,false,true,true,false],
field6:["sdfdsf","sdfdsfdsf","rtyjyjtyj","wrtwrt","wrtw"],
index:[1,2,3,4,5]
};
}])
.service('$p',['$q',function($q) {
this.ajax = function (x,timeout) {
return async(x,timeout);
}
this.eajax = function (x,timeout) {
return easync(x,timeout);
}
var async = function(name,timeout) {
var deferred = $q.defer();
setTimeout(function() {
//deferred.notify('About to greet ' + name + '.');
deferred.resolve(name);
//deferred.reject('Greeting ' + name + ' is not allowed.');
}, timeout?timeout:1000);
return deferred.promise;
}
var easync = function(name,timeout) {
var deferred = $q.defer();
setTimeout(function() {
//deferred.notify('About to greet ' + name + '.');
//deferred.resolve(name);
deferred.reject('api error');
}, timeout?timeout:1000);
return deferred.promise;
}
}]);
<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>
/* Styles go here */
.t-datasheet{
border: 1px solid #e4e4e4;
padding:2px;
overflow:scroll;
}
.t-datasheet table{
border-collapse:collapse;
font-family:Tahoma;
font-size:9pt;
table-layout:fixed;
}
.t-datasheet table td{
border:1px solid #000;
}
.spinner{
background: url(https://stanfy.com/wp-content/uploads/2015/09/1-V3h-VWthi5lL0QySF6qZPw.gif) center center no-repeat;
background-size:40px 40px;
}