草庐IT

javascript - 如何使其他指令在 uib-tab 元素内工作

coder 2025-03-14 原文

是否有 uib-tab 指令的回调函数可用于在呈现选项卡后刷新内部指令?

我试图找到当我在 angular-bootstrap 提供的 uib-tab 指令中使用该指令时出现的第三方指令问题的根源。第三方指令是angular-multi-slider该问题首次报告于 that repository .

可用案例in plnkr .单击第二个选项卡,您会看到内部 slider 的所有 handle 都在其他 handle 之上(即,宽度 = 0px)。然后单击其中一个 handle ,它就会正确显示。即使按照您关于 FAQ 中的范围的建议,问题仍然存在.

Angular 应用

'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);
    }
  }
});

注意事项: 我使用的 AngularJS 版本是 1.4.7。 angular-bootstrap 的版本是 0.14.3。 angular-multi-slider的版本是0.1.1

最佳答案

问题是第二个选项卡上的 slider 正在渲染并在选项卡内容区域可见之前点击 multislider.js - updateCaclulations(或计算 slider handle 之间空间的任何函数)函数。所以没有父空间可以计算。这plunk演示了如何使用 ng-if 仅在选项卡处于事件状态时呈现多 slider 。由于 SO 不会让您在没有代码的情况下发布答案,所以这里是:

    <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];

关于javascript - 如何使其他指令在 uib-tab 元素内工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33642247/

有关javascript - 如何使其他指令在 uib-tab 元素内工作的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  5. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  6. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  7. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  8. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  9. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  10. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

随机推荐