How to get HTML compiled inside a directive

3 071 peržiūra
Praleisti ir pereiti prie pirmo neskaityto pranešimo

Tanguy Krotoff

neskaityta,
2013-02-18 15:01:352013-02-18
kam: ang...@googlegroups.com
Hi all,

I don't understand how to get "compiled" HTML inside my directive (parameter element of the link function).
I would like the same thing but for the whole HTML that is passed to the directive.



Simplified example:

The idea: a user selects a color via a jQuery plugin that wraps a regular HTML select.

// Defines the available colors for the HTML select
app.constant('EventColors', {
    Green:      '#7bd148'
    BoldBlue:   '#5484ed'
  }
);

// The controller that injects the available colors into the scope
function EventColorsCtrl($scope, EventColors) {
  $scope.EventColors = EventColors;
}

// The view that contains a form with a select
<select simplecolorpicker ng-model="color" ng-controller="EventColorsCtrl">
  <option value="{{EventColors.Green}}">Green</option>
  <option value="{{EventColors.BoldBlue}}">Bold blue</option>
</select>

// The directive that wraps a jQuery plugin (simplecolorpicker) that transforms the HTML select into a nice color picker
app.directive('simplecolorpicker', function() {
  return {
    restrict: 'A',
    require: 'ngModel',

    link: function(scope, element, attrs, ngModel) {
      element.simplecolorpicker(); // <--- element param contains the non compiled expressions, I get {{EventColors.Green}} instead of #7bd148
    }
  };
});


The best solution that I could find so far is to use setTimeout() inside the directive:

app.directive('simplecolorpicker', function() {
  return {
    restrict: 'A',
    require: 'ngModel',

    link: function(scope, element, attrs, ngModel) {
      // Wait for the AngularJS expressions inside element to be compiled
      setTimeout(function() {
        element.simplecolorpicker(); // <-- It works, I get #7bd148
      }, 0); // Works with no delay
    }
  };
});


Is there a better solution to get the expressions compiled for my jQuery plugin?

Peter Bacon Darwin

neskaityta,
2013-02-19 01:11:552013-02-19
kam: ang...@googlegroups.com

Try requiring the ng-model controller and overriding $render

Pete
...from my mobile.

--
You received this message because you are subscribed to the Google Groups "AngularJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to angular+u...@googlegroups.com.
To post to this group, send email to ang...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular?hl=en-US.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Tanguy Krotoff

neskaityta,
2013-02-19 06:05:362013-02-19
kam: ang...@googlegroups.com
If you check the example on plunker I already use ngModel.$render and ngModel.$viewValue.
But this does not get me the element parameter as compiled HTML :/

KM

neskaityta,
2013-02-19 09:51:482013-02-19
kam: ang...@googlegroups.com
I've been playing with this few a little bit now.
This particular jQuery plugin is a bit challenging to wrap because it doesn't really have a data model.

It relies on the pre-existing DOM to keep its options.

You could actually rewrite the plugin itself for Angular and it would work well and probably be very easy to write as an Angular directive.

You could also amend the existing plugin to accept an array of objects and then build it's own option list from them. The directive could look something like simplecolorpicker="EventColors". Let the plugin rebuild its options when given a new set of options.

It's hard to just wrap things up that want to use the DOM for their data model. :(

Cool colorpicker by the way. I like it a lot. If it were my work I'd probably rewrite it to lean on AngularJS, since it's fairly simple.

I recently experimented with wrapping bootstrap's typeahead for Angular.. not a general solution, I just want to see what it would be like.


Maybe it will help you, I was able to just tie the appropriate callbacks in and call it a day.
But autocompletion is less stateful than something like a colorpicker.

Tanguy Krotoff

neskaityta,
2013-02-19 11:52:482013-02-19
kam: ang...@googlegroups.com
KM wrote:
It relies on the pre-existing DOM to keep its options.

That's what I like about it: just write a regular select like you would normaly do and call simplecolorpicker() to make it pretty.
The select HTML tag IS the data instead of using a JavaScript object.

You could actually rewrite the plugin itself for Angular and it would work well and probably be very easy to write as an Angular directive.

You could also amend the existing plugin to accept an array of objects and then build it's own option list from them. The directive could look something like simplecolorpicker="EventColors". Let the plugin rebuild its options when given a new set of options.

In both cases I guess I cannot keep the mechanism of relying on HTML select tag for the data :/

Meanwhile I have updated the plunker so is that it works well with setTimeout():

    link: function(scope, element, attrs, ngModel) {
      var colorPicker = null;
      var initialSelectedColor = null;

      function selectColor(color) {
        initialSelectedColor = null;
        element.val(color);
        element.simplecolorpicker('selectColor', element.val());
      }

      // HACK Wait for the AngularJS expressions inside element to be compiled
      setTimeout(function() {
        colorPicker = element.simplecolorpicker();
        if (initialSelectedColor !== null) {
          // Initializes the colorpicker with a color if one exists
          selectColor(initialSelectedColor);
        }

        // View -> model
        colorPicker.on('change', function() {
          scope.$apply(function() {
            ngModel.$setViewValue(element.val());
          });
        });
      }, 0); // Works with no delay

      // Model -> view
      ngModel.$render = function() {
        if (colorPicker !== null) {
          selectColor(ngModel.$viewValue);
        } else {
          initialSelectedColor = ngModel.$viewValue;
        }
      };

      // Cleanup
      element.on('$destroy', function() {
        if (colorPicker !== null) {
          colorPicker('destroy');
        }
      });
    }

I don't even understand why using setTimeout() works since there is no delay (0).

The best would be to attach to a AngularJS event that says "HTML compiled, all expressions evaluated".
I have tried $viewContentLoaded and $includeContentLoaded events (https://github.com/angular/angular.js/issues/734) without luck.

Thanks for your explanations, it is way more clear now in my head!

Peter Bacon Darwin

neskaityta,
2013-02-19 14:58:322013-02-19
kam: ang...@googlegroups.com
So the deal is this.  The {{}} curly bracket interpolation is computed during the next $digest cycle, which happens after compilation has completed - after the link function has been executed.
Moreover, while the first call to $rende is also in this $digest, it is not guaranteed that it will be called after the interpolations have been done.
So you cannot rely on link or $render (or even $evalAsync) to ensure that the interpolations have occurred.
I think in this case it is correct to use a timeout, although I would use the built-in $timeout service.
Also I agree that it would probably be pretty easy to write this picker in pure AngularJS.
Pete

KM

neskaityta,
2013-02-20 16:46:212013-02-20
kam: ang...@googlegroups.com
Hey Tanguy Krotoff,

I had some time so I played with your plunker and implemented your color picker as an angular directive.

It's far from perfect, since this is quick and dirty and I probably don't yet know the most efficient ways to lay this stuff out yet.

I hope it sets you on the right track, have a look:
http://plnkr.co/edit/zlP0RSH3m0ghsefHeaLI?p=preview

If anyone else has any feedback on how the process could be streamlined, I'd be interested to hear it.

--KGZM

Tanguy Krotoff

neskaityta,
2013-02-25 04:58:362013-02-25
kam: ang...@googlegroups.com
Thanks for your replies!
Atsakyti visiems
Atsakyti autoriui
Persiųsti
0 naujų pranešimų