How to make other directives work inside uib-tab elements

Is there a callback function for uib-tab directives that I can use to update internal directives after the tab is displayed?

I am trying to find the source of the problem with the third-party directive that appears when I use this directive inside the uib-tab directive provided by angular-bootstrap. The third-party directive is angular-multi-slider , and the problem was first reported to this repository .

Use case available in plnkr . Click the second tab and you will see that the internal slider has all its handles one on top of the others (i.e. widths = 0px). Then click on one of the handles and it will appear correctly. The problem persists even after following your recommendation regarding areas in the FAQ .

Angular Application

'use strict'; angular.module('multiSliderDemo', ['angularMultiSlider', 'ngAnimate', 'ui.bootstrap']); angular.module('multiSliderDemo') .controller('DemoCtrl', function ($rootScope, $scope, $sce, $uibModal) { var s = [ {value: 2, title:"Brainstorming", component: "Proposal Making", symbol: $sce.trustAsHtml("1")}, {value: 50, title:"Working groups formation", component: "Proposal Making", symbol: $sce.trustAsHtml("2")}, {value: 100, title:"Proposal drafting",component:"Proposal Making", symbol: $sce.trustAsHtml("3")}, {value: 130, title:"Proposal editing", component: "Versioning", symbol: $sce.trustAsHtml("4")}, {value: 160, title:"Proposal selection", component: "Versioning", symbol: $sce.trustAsHtml("5")}, {value: 200, title:"Discussion of proposals", component: "Deliberation", symbol: $sce.trustAsHtml("6")}, {value: 250, title:"Technical assessment", component: "Deliberation", symbol: $sce.trustAsHtml("7")}, {value: 300, title:"Voting on proposals", component: "Voting", symbol: $sce.trustAsHtml("8")} ]; $scope.app = {sliders:s} }); 

index.html

 <html ng-app="multiSliderDemo"> <head> <meta charset="UTF-8"> <title>Multi Slider</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="multislider.css"> </head> <body> <div ng-controller="DemoCtrl" class="container"> <article> <h2>Multi-Slider Issue with uib-tabs</h2> <form name="sliderForm" id="sliderForm" novalidate autocomplete="off"> <fieldset class="row"> <uib-tabset> <uib-tab heading="Tab 1" active="true"> <multi-slider name="mySlider" floor="0" step="1" precision="2" ceiling="365" bubbles="true" ng-model="app.sliders"> </multi-slider> </uib-tab> <uib-tab heading="Tab 2" active="false"> <section class="col-sm-6 padding-10"> <multi-slider name="mySlider" floor="0" step="1" precision="2" ceiling="365" bubbles="true" ng-model="app.sliders"> </multi-slider> </section> </uib-tab> </uib-tabset> </fieldset> </form> </article> </div> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular-animate.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.min.js"></script> <script src="multislider.js"></script> <script src="script.js"></script> </body> </html> 

CSS

 .angular-multi-slider { display: inline-block; position: relative; height: 5px; width: 100%; margin: 25px 5px 25px 5px; vertical-align: middle; } .angular-multi-slider div { white-space: nowrap; position: absolute; } .angular-multi-slider div.bar { width: 100%; height: 100%; border-radius: 6px; background: #999; overflow: hidden; } .angular-multi-slider div.handle { cursor: pointer; width: 10px; height: 30px; top: -15px; background-color: #13b6ff; /*can override with color in slider object*/ border: 2px solid #000; z-index: 2; border-radius: 4px; -o-transition: .3s; -ms-transition: .3s; -moz-transition: .3s; -webkit-transition: .3s; -webkit-transition-property: background-color; transition-property: background-color; } .angular-multi-slider div.handle:hover, .angular-multi-slider div.handle:focus, .angular-multi-slider div.handle:active, .angular-multi-slider div.handle.active { -webkit-filter: brightness(70%); filter: brightness(70%); } .angular-multi-slider div.handle:hover + .bubble, .angular-multi-slider div.handle:focus + .bubble, .angular-multi-slider div.handle.grab + .bubble, .angular-multi-slider div.handle:hover, .angular-multi-slider div.handle:focus, .angular-multi-slider div.handle.grab { -webkit-transform: scale(1.1); transform: scale(1.1); z-index: 9999; } .angular-multi-slider div.handle.grab + .bubble, .angular-multi-slider div.handle.grab{ background-color: rgba(0,0,0,1); } .angular-multi-slider div.bubble { display: none; cursor: default; top: -36px; padding: 1px 3px 1px 3px; font-size: 0.7em; font-family: sans-serif; -o-transition: .1s; -ms-transition: .1s; -moz-transition: .1s; -webkit-transition: .1s; -webkit-transition-property: top; transition-property: top; } .angular-multi-slider div.bubble:nth-child(2) { top: 34px !important; z-index:9999; } .angular-multi-slider div.bubble.active { display: inline-block; color: #fff; font-size:12px; font-family: 'Arial', sans-serif; text-align: center; background-color: rgba(0,0,0,0.75); border-radius: 8px; padding: 3px 8px; } .angular-multi-slider div.limit { margin-top: 12px; color: #000; font-weight: bold; } 

Multislider.js

  'use strict'; angular.module('angularMultiSlider', []) .directive('multiSlider', function($compile, $timeout) { var events = { mouse: { start: 'mousedown', move: 'mousemove', end: 'mouseup' }, touch: { start: 'touchstart', move: 'touchmove', end: 'touchend' } }; function roundStep(value, precision, step, floor) { var remainder = (value - floor) % step; var steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder; var decimals = Math.pow(10, precision); var roundedValue = steppedValue * decimals / decimals; return parseFloat(roundedValue.toFixed(precision)); } function offset(element, position) { return element.css({ left: position }); } function pixelize(position) { return parseInt(position) + "px"; } function contain(value) { if (isNaN(value)) return value; return Math.min(Math.max(0, value), 100); } return { restrict: 'EA', require: '?ngModel', scope: { floor : '@', ceiling : '@', step : '@', precision : '@', bubbles : '@', sliders : '=ngModel' }, template : '<div class="bar"></div>' + '<div class="limit floor">{{ floor }}</div>' + '<div class="limit ceiling">{{ ceiling }}</div>', link : function(scope, element, attrs, ngModel) { if (!ngModel) return; // do nothing if no ng-model //base copy to see if sliders returned to original var original; ngModel.$render = function() { original = angular.copy(scope.sliders); }; element.addClass('angular-multi-slider'); // DOM Components var sliderStr = ''; angular.forEach(scope.sliders, function(slider, key){ sliderStr += ('<div class="handle"> </div> <div class="bubble">{{ sliders[' + key.toString() + '].title }}{{ sliders[' + key.toString() + '].value}} </div>'); }); var sliderControls = angular.element(sliderStr); element.append(sliderControls); $compile(sliderControls)(scope); var children = element.children(); var bar = angular.element(children[0]), ngDocument = angular.element(document), floorBubble = angular.element(children[1]), ceilBubble = angular.element(children[2]), bubbles = [], handles = []; //var sliderChildren = sliderControls.children(); angular.forEach(scope.sliders, function(slider, key) { handles.push(angular.element(children[(key * 2) + 3])); bubbles.push(angular.element(children[(key * 2) + 4])); }); // Control Dimensions Used for Calculations var handleHalfWidth = 0, barWidth = 0, minOffset = 0, maxOffset = 0, minValue = 0, maxValue = 0, valueRange = 0, offsetRange = 0; if (scope.step === undefined) scope.step = 1; if (scope.floor === undefined) scope.floor = 0; if (scope.ceiling === undefined) scope.ceiling = 500; if (scope.precision === undefined) scope.precision = 0; if (scope.bubbles === undefined) scope.bubbles = false; var bindingsSet = false; var updateCalculations = function() { scope.floor = roundStep(parseFloat(scope.floor), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)); scope.ceiling = roundStep(parseFloat(scope.ceiling), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)); angular.forEach(scope.sliders, function(slider) { slider.value = roundStep(parseFloat(slider.value), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)); }); handleHalfWidth = handles[0][0].offsetWidth / 2; barWidth = bar[0].offsetWidth; minOffset = 0; maxOffset = barWidth - handles[0][0].offsetWidth; minValue = parseFloat(scope.floor); maxValue = parseFloat(scope.ceiling); valueRange = maxValue - minValue; offsetRange = maxOffset - minOffset; }; var updateDOM = function () { updateCalculations(); var percentOffset = function (offset) { return contain(((offset - minOffset) / offsetRange) * 100); }; var percentValue = function (value) { return contain(((value - minValue) / valueRange) * 100); }; var pixelsToOffset = function (percent) { return pixelize(percent * offsetRange / 100); }; var setHandles = function () { offset(ceilBubble, pixelize(barWidth - ceilBubble[0].offsetWidth)); angular.forEach(scope.sliders, function(slider,key){ if (slider.color) { handles[key].css({ "background-color": slider.color }); } offset( handles[key], pixelsToOffset(percentValue(slider.value))); offset( bubbles[key], pixelize(handles[key][0].offsetLeft - (bubbles[key][0].offsetWidth / 2) + handleHalfWidth)); }); }; var bind = function (handle, bubble, currentRef, events) { var onEnd = function () { handle.removeClass('grab'); bubble.removeClass('grab'); if (!(''+scope.bubbles === 'true')) { bubble.removeClass('active'); } ngDocument.unbind(events.move); ngDocument.unbind(events.end); if (angular.equals(scope.sliders, original)) { ngModel.$setPristine(); } scope.$apply(); }; var onMove = function (event) { // Suss out which event type we are capturing and get the x value var eventX = 0; if (event.clientX !== undefined) { eventX = event.clientX; } else if ( event.touches !== undefined && event.touches.length) { eventX = event.touches[0].clientX; } else if ( event.originalEvent !== undefined && event.originalEvent.changedTouches !== undefined && event.originalEvent.changedTouches.length) { eventX = event.originalEvent.changedTouches[0].clientX; } var newOffset = Math.max( Math.min( (eventX - element[0].getBoundingClientRect().left - handleHalfWidth), maxOffset), minOffset), newPercent = percentOffset(newOffset), newValue = minValue + (valueRange * newPercent / 100.0); newValue = roundStep(newValue, parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)); scope.sliders[currentRef].value = newValue; setHandles(); ngModel.$setDirty(); scope.$apply(); }; var onStart = function (event) { updateCalculations(); bubble.addClass('active grab'); handle.addClass('active grab'); setHandles(); event.stopPropagation(); event.preventDefault(); ngDocument.bind(events.move, onMove); return ngDocument.bind(events.end, onEnd); }; handle.bind(events.start, onStart); }; var setBindings = function () { var method, i; var inputTypes = ['touch', 'mouse']; for (i = 0; i < inputTypes.length; i++) { method = inputTypes[i]; angular.forEach(scope.sliders, function(slider, key){ bind(handles[key], bubbles[key], key, events[method]); }); } bindingsSet = true; }; if (!bindingsSet) { setBindings(); // Timeout needed because bubbles offsetWidth is incorrect // during initial rendering of html elements setTimeout( function() { if (''+scope.bubbles === 'true') { angular.forEach(bubbles, function(bubble) { bubble.addClass('active'); }); } //added this for tab 1... updateCalculations(); setHandles(); }, 1); } }; // Watch Models based on mode scope.$watch('sliders', updateDOM); // Update on Window resize window.addEventListener('resize', updateDOM); } } }); 

Notes: The version of AngularJS I am using is 1.4.7. The angular-bootstrap version is 0.14.3. The angular-multi-slider version is 0.1.1

+5
source share
2 answers

The problem is that the slider on the second tab is visualized and clicks on multislider.js - updateCaclulations (or any function that calculates the space between the slider handles) before the tab content area is visible. Thus, there is no parent space to compute. This plunk demonstrates the use of ng-if to only display the multi-leaf when the tab is active. And since SO will not allow you to post an answer using plunk without code, here it is:

  <uib-tabset> <uib-tab heading="Tab 1" active="activeTabs[0]"> <multi-slider name="mySlider" floor="0" step="1" precision="2" ceiling="365" bubbles="true" ng-model="app.sliders" ng-if="activeTabs[0]"> </multi-slider> </uib-tab> <uib-tab heading="Tab 2" active="activeTabs[1]"> <section class="col-sm-6 padding-10"> <multi-slider name="mySlider" floor="0" step="1" precision="2" ceiling="365" bubbles="true" ng-model="app.sliders" ng-if="activeTabs[1]"> </multi-slider> </section> </uib-tab> </uib-tabset> 

controller:

 $scope.activeTabs = [true, false]; 
+6
source

I looked at it for more than an hour and tried a bunch of different things, and none of them worked. It would be easy to blame the third-party library, but I suspect that this is also related to how the tabs are displayed (i.e. Via replace: true ). Your problem will not be the first problem that users have problems with the contents of the tab. We need to come up with best practices for tab content - especially when users put complex directives in the content.

+1
source

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


All Articles