4

I am working on my first angularjs directive. I was hoping to wrap jquery-steps (https://github.com/rstaib/jquery-steps) into a directive. My issue comes when I try to bind inputs or expression inside the content of the steps to controller models they don't bind. An example code of what I have is below.

angular.module('foobar',[])
.controller 'UserCtrl', ($scope) ->
    $scope.user =
       name:'John Doe'

.directive 'wizardForm', () ->
   return {
      restrict: 'A',
      link: (scope, ele) ->
        ele.steps({})
    }

The html looks as follows

<div ng-controller="UserCtrl">    

  <div class='vertical' wizard-form>
     <h1> Step 1 </h1>
     <div>Name: {{user.name}}</div>

     <h1> Step 2 </h1>
     <div> Advanced Info etc</div>
  </div>
</div>

The output for the content in step 1 is Name: {{user.name}}

I am still a beginner with angular so I cannot seem to understand why there is no scope or model attached to the content area. Any tips or leads to get me on the right track would be very helpful!

EDIT: I added a plnkr to show what I have tried. http://plnkr.co/edit/y60yZI0oBjW99bBgS7Xd

majorlazer
  • 118
  • 1
  • 7
  • is it giving console error? – Pankaj Parkar Aug 05 '14 at 02:04
  • you really haven't shown enough code to present a problem. Any interaction with scope by code outside of angular needs to use `$apply()` – charlietfl Aug 05 '14 at 03:19
  • @pankajparkar I am not getting an console errors. – majorlazer Aug 05 '14 at 05:09
  • @charlietfl I am not messing with scope outside of angular. When the directive runs. It takes the the content in the element and creates applies the .steps() method from jquery-steps. For some reason, the generated wizard from jquery-steps is not using the scope to bind. It is verbatim outputting Name: {{user.name}} – majorlazer Aug 05 '14 at 05:12
  • when that happens it means it hasn't been through angula `$compile` so you would need to use `$compile()` yourself – charlietfl Aug 05 '14 at 13:22
  • @charlietfl I created a plnkr to show what I have tried. http://plnkr.co/edit/y60yZI0oBjW99bBgS7Xd – majorlazer Aug 05 '14 at 18:22
  • instead of putting in directive make it app.run(function(){ $(".vertical").steps(); }); – Pankaj Parkar Aug 05 '14 at 20:04

5 Answers5

4

The following resolved this issue in my project:

.directive('uiWizardForm', ['$compile', ($compile) ->
    return {
        link: (scope, ele) ->
            ele.wrapInner('<div class="steps-wrapper">')
            steps = ele.children('.steps-wrapper').steps()
            $compile(steps)(scope)
    }
])
Hugo Mallet
  • 533
  • 5
  • 8
4

kudos to Hugo Mallet and Nigel Sheridan-Smith. However, here was a simpler method if you want to include event handling.

.directive("uiWizardForm", function()
{
    var scope;

    return {
        restrict: "A",
        controller:function($scope){
            scope = $scope;
        },
        compile: function($element){
            $element.wrapInner('<div class="steps-wrapper">')
            var steps = $element.children('.steps-wrapper').steps({
                onStepChanging: function (event, currentIndex, newIndex)
                {
                    return scope.onStepChanging();
                },
                onFinishing: function (event, currentIndex)
                {
                    return scope.onFinishing();
                },
                onFinished: function (event, currentIndex)
                {
                    scope.finishedWizard();
                }
            });
        }
    };
});

PS. you don't need to use the wrapInner if you already have the added in your template.

ggedde
  • 582
  • 5
  • 12
2

Based on Hugo's answer, if you also want to use the jQuery steps event handling, you should do it this way (with Coffeescript):

.directive('uiWizardForm', ['$compile', ($compile) ->
return {
    scope: {
        stepChanging: '&',
        stepChanged: '&',
        finished: '&'
    },
    compile: (tElement, tAttrs, transclude) ->
      tElement.wrapInner('<div class="steps-wrapper">')
      steps = tElement.children('.steps-wrapper').steps({})

      return {
        pre: (scope, ele, attrs) ->

        post: (scope, ele, attrs) ->
          # Post-link function

          ele.children('.steps-wrapper').on 'stepChanged', () ->
            scope.$apply ->
              return scope.stepChanging() if tAttrs.stepChanging?
              true

          ele.children('.steps-wrapper').on 'finished', () ->
            scope.$apply ->
              scope.finished() if tAttrs.finished?

          ele.children('.steps-wrapper').on 'stepChanging', () ->
            scope.$apply ->
              scope.stepChanging() if tAttrs.stepChanging?
              true

      }
  }
])

Then you can attach your event handlers to the scope on the directive... e.g.:

<ui-wizard-form step-changing='stepChanging()'> maps to the $scope.stepChanging(...) -> function.

2
link: function(scope, elem, attrs){
    elem.wrapInner(_handler.generateTemplate());

    var _steps = elem.children('.vertical').steps({
            headerTag: 'h1',
            bodyTag: 'div'
          });

    $compile(_steps)(scope);
  }

Here's on Plnkr complete solution.

Ihor Pavlyk
  • 1,111
  • 13
  • 10
  • Your example is working pretty fine with me, i just want to ask what if want to replace the Pagination links with buttons that do some code how can i load the next step using the scope variable somethink like .. $scope.steps.NextStep – Tarek Abdel Maqsoud Oct 12 '16 at 10:11
-2

AngularJS's official site provides a step-by-step tutorial on creating custom directive. I trust if you follow through the tutorial, your question will be resolved. Particularly, please pay attention to "Isolated Scope" and "template". I guess these two issues are very relevant to your question. Good luck!

gm2008
  • 4,245
  • 1
  • 36
  • 38