Fixed scrolling issues and page resizing issues. When page resizes the number of visible rows in each table expands.

This commit is contained in:
chouseknecht 2014-05-08 19:15:18 -04:00
parent 79ef9e5ed5
commit 9e645bdb74
3 changed files with 315 additions and 196 deletions

View File

@ -17,7 +17,8 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
event_queue = [],
processed_events = [],
scope = $scope,
api_complete = false;
api_complete = false,
refresh_count = 0;
scope.plays = [];
scope.tasks = [];
@ -28,6 +29,8 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
scope.auto_scroll = false;
scope.searchTaskHostsEnabled = true;
scope.searchSummaryHostsEnabled = true;
scope.hostTableRows = 120;
scope.hostSummaryTableRows = 120;
event_socket = Socket({
scope: scope,
@ -177,79 +180,69 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
}
});
Wait('start');
// Load the job record
Rest.setUrl(GetBasePath('jobs') + job_id + '/');
Rest.get()
.success(function(data) {
job = data;
scope.job_template_name = data.name;
scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : '';
scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : '';
scope.job_template_url = '/#/job_templates/' + data.unified_job_template;
scope.inventory_url = (scope.inventory_name && data.inventory) ? '/#/inventories/' + data.inventory : '';
scope.project_url = (scope.project_name && data.project) ? '/#/projects/' + data.project : '';
scope.job_type = data.job_type;
scope.playbook = data.playbook;
scope.credential = data.credential;
scope.cloud_credential = data.cloud_credential;
scope.forks = data.forks;
scope.limit = data.limit;
scope.verbosity = data.verbosity;
scope.job_tags = data.job_tags;
if (scope.removeLoadJob) {
scope.removeLoadJob();
}
scope.removeLoadJobRow = scope.$on('LoadJob', function() {
Wait('start');
// Load the job record
Rest.setUrl(GetBasePath('jobs') + job_id + '/');
Rest.get()
.success(function(data) {
job = data;
scope.job_template_name = data.name;
scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : '';
scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : '';
scope.job_template_url = '/#/job_templates/' + data.unified_job_template;
scope.inventory_url = (scope.inventory_name && data.inventory) ? '/#/inventories/' + data.inventory : '';
scope.project_url = (scope.project_name && data.project) ? '/#/projects/' + data.project : '';
scope.job_type = data.job_type;
scope.playbook = data.playbook;
scope.credential = data.credential;
scope.cloud_credential = data.cloud_credential;
scope.forks = data.forks;
scope.limit = data.limit;
scope.verbosity = data.verbosity;
scope.job_tags = data.job_tags;
// In the case that the job is already completed, or an error already happened,
// populate scope.job_status info
scope.job_status.status = data.status;
scope.job_status.started = data.started;
scope.job_status.status_class = ((data.status === 'error' || data.status === 'failed') && data.job_explanation) ? "alert alert-danger" : "";
scope.job_status.finished = data.finished;
scope.job_status.explanation = data.job_explanation;
if (data.started && data.finished) {
scope.job_status.elapsed = GetElapsed({
start: data.started,
end: data.finished
});
}
else {
scope.job_status.elapsed = '00:00:00';
}
// In the case that the job is already completed, or an error already happened,
// populate scope.job_status info
scope.job_status.status = data.status;
scope.job_status.started = data.started;
scope.job_status.status_class = ((data.status === 'error' || data.status === 'failed') && data.job_explanation) ? "alert alert-danger" : "";
scope.job_status.finished = data.finished;
scope.job_status.explanation = data.job_explanation;
if (data.started && data.finished) {
scope.job_status.elapsed = GetElapsed({
start: data.started,
end: data.finished
});
}
else {
scope.job_status.elapsed = '00:00:00';
}
scope.$emit('JobReady', data.related.job_events + '?page_size=50&order_by=id');
scope.$emit('GetCredentialNames', data);
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve job: ' + $routeParams.id + '. GET returned: ' + status });
});
scope.$emit('JobReady', data.related.job_events + '?page_size=50&order_by=id');
scope.$emit('GetCredentialNames', data);
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve job: ' + $routeParams.id + '. GET returned: ' + status });
});
});
scope.selectPlay = function(id) {
SelectPlay({
scope: scope,
id: id
});
};
scope.selectTask = function(id) {
SelectTask({
scope: scope,
id: id
});
};
$("#hosts-slider-vertical").slider({
orientation: "vertical",
range: "min",
min: 0,
max: 100,
value: 60,
slide: function( event, ui ) {
$( "#amount" ).val( ui.value );
if (scope.removeRefreshCompleted) {
scope.removeRefreshCompleted();
}
scope.removeRefreshCompleted = scope.$on('RefreshCompleted', function() {
refresh_count++;
if (refresh_count === 2) {
scope.$emit('LoadJob');
}
});
function adjustSize() {
scope.adjustSize = function() {
var height, ww = $(window).width();
if (ww < 1240) {
$('#job-summary-container').hide();
@ -268,32 +261,144 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
$('#job-summary-container').css({ "width": "41.66666667%", "padding-right": "15px", "z-index": 0 }).show();
}
// Adjust page height
// Detail table height adjusting. First, put page height back to 'normal'.
$('#plays-table-detail').height(150);
$('#plays-table-detail').mCustomScrollbar("update");
$('#tasks-table-detail').height(150);
$('#tasks-table-detail').mCustomScrollbar("update");
$('#hosts-table-detail').height(150);
$('#hosts-table-detail').mCustomScrollbar("update");
height = ($('#wrap').height() - $('.site-footer').height()) - $('.main-container').height() - 22;
scope.hostTableRows = 120;
height = $('#wrap').height() - $('.site-footer').outerHeight() - $('.main-container').height();
if (height > 15) {
// there's a bunch of white space at the bottom, let's use it
$('#plays-table-detail').height(150 + (height / 3));
$('#plays-table-detail').mCustomScrollbar("update");
//$('#plays-table-detail').mCustomScrollbar("update");
$('#tasks-table-detail').height(150 + (height / 3));
$('#tasks-table-detail').mCustomScrollbar("update");
// Host details
$('#hosts-table-detail').height(150 + (height / 3));
scope.hostTableRows = Math.max(Math.ceil((150 + (height / 3)) / 20) * 3, 120);
}
refreshHostRows();
// Summary table height adjusting.
height = ($('#job-detail-container').height() / 2) - $('#hosts-summary-section .header').outerHeight() - $('#hosts-summary-section .table-header').outerHeight() - 20;
scope.hostSummaryTableRows = Math.max(Math.ceil(height / 20), 120);
$('#hosts-summary-table').height(height);
refreshSummaryHostRows();
};
function refreshHostRows() {
var url;
if (scope.activeTask) {
scope.hostResults = [];
scope.auto_scroll = true;
url = GetBasePath('jobs') + job_id + '/job_events/?parent=' + scope.activeTask + '&';
url += (scope.task_host_name) ? 'host__name__icontains=' + scope.task_host_name + '&' : '';
url += 'host__isnull=false&page_size=' + scope.hostTableRows + '&order_by=host__name';
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function(data) {
data.results.forEach(function(row) {
scope.hostResults.push({
id: row.id,
status: ( (row.failed) ? 'failed': (row.changed) ? 'changed' : 'successful' ),
host_id: row.host,
task_id: row.parent,
name: row.event_data.host,
created: row.created,
msg: ( (row.event_data && row.event_data.res) ? row.event_data.res.msg : '' )
});
});
$('#hosts-table-detail').mCustomScrollbar("update");
setTimeout( function() { $('#hosts-table-detail').mCustomScrollbar("scrollTo", "bottom"); }, 700);
Wait('stop');
scope.$emit('RefreshCompleted');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
else {
$('#hosts-table-detail').mCustomScrollbar("update");
scope.$emit('RefreshCompleted');
}
}
$(document).ready(function() {
adjustSize();
});
function refreshSummaryHostRows() {
if (scope.hosts.length < scope.hostSummaryTableRows) {
var url = GetBasePath('jobs') + job_id + '/job_host_summaries/?';
url += (scope.summary_host_name) ? 'host__name__icontains=' + scope.summary_host_name + '&': '';
url += '&page_size=' + scope.hostSummaryTableRows + '&order_by=host__name';
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function(data) {
data.results.forEach(function(row) {
var found = false;
scope.hosts.every(function(host) {
if (host.id === row.host) {
found = true;
return false;
}
return true;
});
if (!found) {
scope.hosts.push({
id: row.host,
name: row.summary_fields.host.name,
ok: row.ok,
changed: row.changed,
unreachable: row.dark,
failed: row.failures
});
}
});
scope.hosts.sort(function(a,b) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
$('#hosts-summary-table').mCustomScrollbar("update");
setTimeout( function() { $('#hosts-summary-table').mCustomScrollbar("scrollTo", "bottom"); }, 700);
Wait('stop');
scope.$emit('RefreshCompleted');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
else {
$('#hosts-table-detail').mCustomScrollbar("update");
scope.$emit('RefreshCompleted');
}
}
setTimeout(function() { scope.adjustSize(); }, 500);
// Use debounce for the underscore library to adjust after user resizes window.
$(window).resize(_.debounce(function(){
adjustSize();
scope.adjustSize();
}, 500));
$scope.selectPlay = function(id) {
SelectPlay({
scope: scope,
id: id
});
};
$scope.selectTask = function(id) {
SelectTask({
scope: scope,
id: id
});
};
$scope.toggleSummary = function(hide) {
var docw, doch, height = $('#job-detail-container').height(), slide_width;
if (!hide) {
@ -325,56 +430,64 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
}
};
$scope.HostDetailOnTotalScroll = function(mcs) {
var url = GetBasePath('jobs') + job_id + '/job_events/?parent=' + scope.activeTask;
url += '&host__name__gt=' + scope.hostResults[scope.hostResults.length - 1].name + '&host__isnull=false&page_size=5&order_by=host__name';
if (!scope.auto_scroll) {
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function(data) {
setTimeout(function() {
scope.$apply(function() {
data.results.forEach(function(row) {
scope.hostResults.push({
id: row.id,
status: ( (row.failed) ? 'failed': (row.changed) ? 'changed' : 'successful' ),
host_id: row.host,
task_id: row.parent,
name: row.event_data.host,
created: row.created,
msg: ( (row.event_data && row.event_data.res) ? row.event_data.res.msg : '' )
});
if (scope.hostResults.length > 10) {
scope.hostResults.splice(0,1);
}
$scope.HostDetailOnTotalScroll = _.debounce(function() {
// Called when user scrolls down (or forward in time). Using _.debounce
var url, mcs = arguments[0];
scope.$apply(function() {
if (!scope.auto_scroll && scope.activeTask && scope.hostResults) {
scope.auto_scroll = true;
url = GetBasePath('jobs') + job_id + '/job_events/?parent=' + scope.activeTask + '&';
url += (scope.task_host_name) ? 'host__name__icontains=' + scope.task_host_name + '&' : '';
url += 'host__name__gt=' + scope.hostResults[scope.hostResults.length - 1].name + '&host__isnull=false&page_size=' + (scope.hostTableRows / 3) + '&order_by=host__name';
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function(data) {
data.results.forEach(function(row) {
scope.hostResults.push({
id: row.id,
status: ( (row.failed) ? 'failed': (row.changed) ? 'changed' : 'successful' ),
host_id: row.host,
task_id: row.parent,
name: row.event_data.host,
created: row.created,
msg: ( (row.event_data && row.event_data.res) ? row.event_data.res.msg : '' )
});
//$('#hosts-table-detail').mCustomScrollbar("update");
if (data.next) {
// there are more rows. move dragger up, letting user know.
setTimeout(function() { $('#hosts-table-detail .mCSB_dragger').css({ top: (mcs.draggerTop - 10) + 'px'}); }, 700);
if (scope.hostResults.length > scope.hostTableRows) {
scope.hostResults.splice(0,1);
}
});
}, 100);
Wait('stop');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
scope.auto_scroll = false;
};
if (data.next) {
// there are more rows. move dragger up, letting user know.
setTimeout(function() { $('#hosts-table-detail .mCSB_dragger').css({ top: (mcs.draggerTop - 15) + 'px'}); }, 700);
}
scope.auto_scroll = false;
Wait('stop');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
else {
scope.auto_scroll = false;
}
});
}, 300);
$scope.HostDetailOnTotalScrollBack = function(mcs) {
var url = GetBasePath('jobs') + job_id + '/job_events/?parent=' + scope.activeTask;
Wait('start');
url += '&host__name__lt=' + scope.hostResults[0].name + '&host__isnull=false&page_size=5&order_by=-host__name';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
setTimeout(function() {
scope.$apply(function() {
$scope.HostDetailOnTotalScrollBack = _.debounce(function() {
// Called when user scrolls up (or back in time)
var url, mcs = arguments[0];
scope.$apply(function() {
if (!scope.auto_scroll && scope.activeTask && scope.hostResults) {
scope.auto_scroll = true;
url = GetBasePath('jobs') + job_id + '/job_events/?parent=' + scope.activeTask + '&';
url += (scope.task_host_name) ? 'host__name__icontains=' + scope.task_host_name + '&' : '';
url += 'host__name__lt=' + scope.hostResults[0].name + '&host__isnull=false&page_size=' + (scope.hostTableRows / 3) + '&order_by=-host__name';
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function(data) {
data.results.forEach(function(row) {
scope.hostResults.unshift({
id: row.id,
@ -385,29 +498,34 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
created: row.created,
msg: ( (row.event_data && row.event_data.res) ? row.event_data.res.msg : '' )
});
if (scope.hostResults.length > 10) {
if (scope.hostResults.length > scope.hostTableRows) {
scope.hostResults.pop();
}
});
$('#hosts-table-detail').mCustomScrollbar("update");
if (data.next) {
// there are more rows. move dragger down, letting user know.
setTimeout(function() { $('#hosts-table-detail .mCSB_dragger').css({ top: (mcs.draggerTop + 10) + 'px' }); }, 700);
setTimeout(function() { $('#hosts-table-detail .mCSB_dragger').css({ top: (mcs.draggerTop + 15) + 'px' }); }, 700);
}
Wait('stop');
scope.auto_scroll = false;
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}, 100);
Wait('stop');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
};
}
else {
scope.auto_scroll = false;
}
});
}, 300);
$scope.HostSummaryOnTotalScroll = function(mcs) {
var url = GetBasePath('jobs') + job_id + '/job_host_summaries/';
url += '?host__name__gt=' + scope.hosts[scope.hosts.length - 1].name + '&page_size=5&order_by=host__name';
if (!scope.auto_scroll) {
var url;
if (!scope.auto_scroll && scope.hosts) {
url = GetBasePath('jobs') + job_id + '/job_host_summaries/?';
url += (scope.summary_host_name) ? 'host__name__icontains=' + scope.summary_host_name + '&': '';
url += 'host__name__gt=' + scope.hosts[scope.hosts.length - 1].name + '&page_size=' + (scope.hostSummaryTableRows / 3) + '&order_by=host__name';
Wait('start');
Rest.setUrl(url);
Rest.get()
@ -423,14 +541,13 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
unreachable: row.dark,
failed: row.failures
});
if (scope.hosts.length > 10) {
if (scope.hosts.length > scope.hostSummaryTableRows) {
scope.hosts.splice(0,1);
}
});
//$('#hosts-summary-table').mCustomScrollbar("update");
if (data.next) {
// there are more rows. move dragger up, letting user know.
setTimeout(function() { $('#hosts-summary-table .mCSB_dragger').css({ top: (mcs.draggerTop - 10) + 'px'}); }, 700);
setTimeout(function() { $('#hosts-summary-table .mCSB_dragger').css({ top: (mcs.draggerTop - 15) + 'px'}); }, 700);
}
});
}, 100);
@ -445,40 +562,43 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
};
$scope.HostSummaryOnTotalScrollBack = function(mcs) {
var url = GetBasePath('jobs') + job_id + '/job_host_summaries/';
Wait('start');
url += '?host__name__lt=' + scope.hosts[0].name + '&page_size=5&order_by=-host__name';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
setTimeout(function() {
scope.$apply(function() {
data.results.forEach(function(row) {
scope.hosts.unshift({
id: row.host,
name: row.summary_fields.host.name,
ok: row.ok,
changed: row.changed,
unreachable: row.dark,
failed: row.failures
var url;
if (!scope.auto_scroll && scope.hosts) {
url = GetBasePath('jobs') + job_id + '/job_host_summaries/?';
url += (scope.summary_host_name) ? 'host__name__icontains=' + scope.summary_host_name + '&': '';
url += 'host__name__lt=' + scope.hosts[0].name + '&page_size=' + (scope.hostSummaryTableRows / 3) + '&order_by=-host__name';
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function(data) {
setTimeout(function() {
scope.$apply(function() {
data.results.forEach(function(row) {
scope.hosts.unshift({
id: row.host,
name: row.summary_fields.host.name,
ok: row.ok,
changed: row.changed,
unreachable: row.dark,
failed: row.failures
});
if (scope.hosts.length > scope.hostSummaryTableRows) {
scope.hosts.pop();
}
});
if (scope.hosts.length > 10) {
scope.hosts.pop();
if (data.next) {
// there are more rows. move dragger down, letting user know.
setTimeout(function() { $('#hosts-summary-table .mCSB_dragger').css({ top: (mcs.draggerTop + 15) + 'px' }); }, 700);
}
});
$('#hosts-summary-table').mCustomScrollbar("update");
if (data.next) {
// there are more rows. move dragger down, letting user know.
setTimeout(function() { $('#hosts-summary-table .mCSB_dragger').css({ top: (mcs.draggerTop + 10) + 'px' }); }, 700);
}
});
}, 100);
Wait('stop');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}, 100);
Wait('stop');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
};
$scope.searchTaskHosts = function() {
@ -487,7 +607,7 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
scope.hostResults = [];
url = GetBasePath('jobs') + $routeParams.id + '/job_events/?parent=' + scope.activeTask;
url += (scope.task_host_name) ? '&host__name__icontains=' + scope.task_host_name : '';
url += '&host__name__isnull=false&page_size=10&order_by=host__name';
url += '&host__name__isnull=false&page_size=' + scope.hostTableRows + '&order_by=host__name';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
@ -530,7 +650,7 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
scope.hosts = [];
url = GetBasePath('jobs') + $routeParams.id + '/job_host_summaries/?';
url += (scope.summary_host_name) ? 'host__name__icontains=' + scope.summary_host_name + '&': '';
url += 'page_size=10&order_by=host__name';
url += 'page_size=' + scope.hostSummaryTableRows + '&order_by=host__name';
Rest.setUrl(url);
Rest.get()
.success(function(data) {

View File

@ -499,7 +499,7 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
return 0;
});
// Only keep 10 hosts
if (scope.hosts.length > 10) {
if (scope.hosts.length > scope.hostSummaryTableRows) {
scope.hosts.splice(0,1);
}
scope.auto_scroll = true;
@ -564,8 +564,8 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
return 0;
});
// Keep the list pruned to 10 hosts
if (scope.hostResults.length === 10) {
// Keep the list pruned to a limited # of hosts
if (scope.hostResults.length === scope.hostTableRows) {
scope.hostResults.splice(0,1);
}
@ -695,25 +695,23 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
}
Wait('start');
scope.hostResults = [];
url = GetBasePath('jobs') + $routeParams.id + '/job_events/?parent=' + id +
'&host__isnull=false&page_size=10&order_by=-host__name';
url = GetBasePath('jobs') + $routeParams.id + '/job_events/?parent=' + id + '&';
url += (scope.task_host_name) ? 'host__name__icontains=' + scope.task_host_name + '&' : '';
url += 'host__isnull=false&page_size=' + scope.hostTableRows + '&order_by=host__name';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
var i;
if (data.results.length > 0) {
for (i = data.results.length - 1; i >=0; i--) {
scope.hostResults.push({
id: data.results[i].id,
status: ( (data.results[i].failed) ? 'failed' : (data.results[i].changed) ? 'changed' : 'successful' ),
host_id: data.results[i].host,
task_id: data.results[i].parent,
name: data.results[i].event_data.host,
created: data.results[i].created,
msg: ( (data.results[i].event_data && data.results[i].event_data.res) ? data.results[i].event_data.res.msg : '' )
});
}
}
data.results.forEach(function(row) {
scope.hostResults.push({
id: row.id,
status: ( (row.failed) ? 'failed' : (row.changed) ? 'changed' : 'successful' ),
host_id: row.host,
task_id: row.parent,
name: row.event_data.host,
created: row.created,
msg: ( (row.event_data && row.event_data.res) ? row.event_data.res.msg : '' )
});
});
Wait('stop');
SelectHost({ scope: scope });
})

View File

@ -727,15 +727,16 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
updateOnContentResize: true
},
scrollButtons: {
enable: true,
scrollType: 'continuous'
enable: true
},
theme: 'dark-thick',
mouseWheel: true,
scrollInertia: 0,
scrollInertia: 300,
callbacks: {
onTotalScroll: scope[attrs.onTotalScroll],
onTotalScrollOfset: attrs.onTotalScrollOffset,
onTotalScrollBack: scope[attrs.onTotalScrollBack],
onTotalScrollBackOffset: attrs.onTotlaScrollBackOffset
}
});
};