1

I have a form which is in a directive. It contains 2 select dropdowns (jQuery styled) and one jQuery UI slider, so it's all consistently themed.

Dropdown 1 is populated at spawn, as simple as they come. Dropdown 2 gets populated when the value of Dropdown 1 changes. This happens because dropdown 1 has ng-change="refreshModels()" attached to it. This works like a charm.

However, sliders are more problematic. In order to get the value of the slider, I need to have a hidden input field which gets populated with $.val() on every change in the slider position. The value updates correctly, and even the change event fires (when I put onchange="console.log('I changed')" onto the hidden field, I get everything logged nicely).

However, the ng-model attached to that very same hidden field never updates. Ng-change never triggers, unlike onchange.

So I asked around and tried doing this in the jQuery slider's slide handler:

angular.element($(this)).scope().$apply(); 

But no dice. Nothing happens. Trying to call a model which I know exists in that directive like so:

angular.element($(this)).scope().model_that_definitely_exists; 

yields undefined. Trying to access a model in the parent controller of the directive also yields undefined.

Why is it so difficult to make Angular register changes made by means other than direct user input on the field? What am I doing wrong?

P.S. I tried putting ng-mouseup onto the element that becomes the slider, and that calls the refresh method fine - but diregards the hidden fields. To Angular, those are still undefined, even though realistically, they have values - the slider put them there.

P.P.S. I also tried putting a $watch onto the model I expect to change after moving the slider, nothing. The model's value never changes, even if the field's val actually does.

P.P.P.S. I just found this sad state of affairs. https://github.com/angular-ui/angular-ui/issues/252 If anyone has a workaround, I'd be much obliged.

Swader
  • 11,387
  • 14
  • 50
  • 84
  • possible duplicate of [Update Angular model after setting input value with jQuery](http://stackoverflow.com/questions/17109850/update-angular-model-after-setting-input-value-with-jquery) – Stewie Jun 23 '13 at 20:16
  • 1
    Don't know if this JSBin helps: http://jsbin.com/azasus/1/edit – imaginaryboy Jun 23 '13 at 20:40

2 Answers2

3

I recently wrote a custom directive for jqueryUiSlider.

You can view it on GitHub ( https://github.com/jvandemo/CoconutJS/blob/master/src/jquery-ui/directives/ccnutJqueryUiSlider.js) but I'll post it here for reference:

angular.module('ccnut.jquery-ui.directives')
    .directive('ccnutJqueryUiSlider', ['ccnut.config', 'logger', function (ccnutConfig, logger) {
        return {
            restrict: 'AC',
            require: 'ngModel',
            link: function (scope, iElement, iAttrs, ngModelController) {

                if (!window.$ || !window.$.fn || !window.$.fn.datepicker) {
                    return logger.warn('ccnutJqueryUiSlider directive skipped: slider function from jQuery UI library not available');
                }

                // Get options
                var options = angular.extend({}, scope.$eval(iAttrs.ccnutJqueryUiSlider));

                // Initialize slider
                iElement.slider(options);

                // Update model on slide event
                iElement.on('slide', function (event, ui) {
                    ngModelController.$setViewValue(ui.value);
                    scope.$apply();
                });

                // Update slider when view needs to be updated
                ngModelController.$render = function () {
                    var value = (ngModelController.$viewValue || 0);
                    iElement.slider('value', value);
                };

            }
        };
    }]);

Here, the ngModelController is used to 'bind' the value of the slider to the model assigned to the directive using ng-model="yourModelName".

Usage is simple like this:

<div ng-model="model" ccnut-jquery-ui-slider="{min:1, max:5}"></div>

This will create a slider and store the value in the model specified by ng-model.

When updating the model, the slider will also slide to the new value.

You can read some extra documentation in the comments of the source code at https://github.com/jvandemo/CoconutJS/blob/master/src/jquery-ui/directives/ccnutJqueryUiSlider.js

Hope that helps!

jvandemo
  • 13,046
  • 2
  • 23
  • 18
  • How would you apply this to range sliders, since only one model can be bound to it? I tried it this way, but the results were less than stellar: http://hastebin.com/qotafaduwi.html – Swader Jun 27 '13 at 23:04
  • I removed the ng-model and some other dependencies and made it range-compatible. Thank you for the prod in the right direction! https://github.com/Swader/ngdirectives – Swader Jun 28 '13 at 08:55
1

Not sure if it's an unfortunate amount of code or not, but I'm inclined to think a decent option is to use a simple directive to create/hook-up the jQueryUI sliders. The simple directive cooked up below sets up a two-way binding between the 'value' attribute on the directive's scope and the 'value' attribute on the controller's scope. The template for the directive is simply the div that gets turned into the slider during the directive's link function, which also sets up a listener for the 'slide' event and copies the value to the scope. The $watch is also set up so that changes from the controller get pushed into the slider's state.

You can view the following sample live via this JSBin (different from what I commented above): http://jsbin.com/ejojad/1/edit

HTML:

<!DOCTYPE html>
<html>
<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
  <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1/themes/smoothness/jquery-ui.min.css" rel="stylesheet" type="text/css" />
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
  <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
  <meta charset=utf-8 />
  <title>JS Bin</title>
</head>
<body>
  <div ng-app="myApp" ng-controller="Ctrl">
    <input type="text" ng-model="value"/>
    <jqui-slider value="value"></jqui-slider>
  </div>
</body>
</html> 

JavaScript:

function Ctrl($scope) {
  $scope.value = 0;
}

function jQuiSliderDirective() {
  return {
    restrict: 'E',
    scope: {
      value: '=value',
    },
    template: '<div></div>',
    replace: true,
    link: function($scope, elem, attrs) {
      var slider = $(elem).slider();
      slider.on('slide', function(event, ui) {
        $scope.$apply(function() {
          $scope.value = ui.value;
        });
      });
      $scope.$watch('value', function(val, old) {
        slider.slider('value', val);
      });
    }
  };
}

var myApp = angular.module('myApp', []);
myApp.directive('jquiSlider', jQuiSliderDirective);
imaginaryboy
  • 5,979
  • 1
  • 32
  • 27
  • For some reason it threw errors "method addClass not defined" from jQuery. Good example, though, thanks - I've learned a lot from this. – Swader Jun 27 '13 at 22:24