2

I am trying to pass dynamic variables into a directive that displays a time picker (pickadate.js: http://amsul.ca/pickadate.js/time/). However, I am struggling on how to get the options into the directive. I am scoured the internet, but see a multitude of approaches, and am confused on how best to construct this as it is not working. Here is my current code:

Directive:

// Pick a date directive used as pick-a-time on HTML element
appDirectives.directive('pickATime', function() {
    return {
        // Restrict it to be an attribute in this case
        restrict: 'A',
        // responsible for registering DOM listeners as well as updating the DOM
        link: function(scope, element, attrs) {
            element.pickatime(scope.$eval(attrs.pickATime));
        },
        scope: {
            timeOptions: '=options'
        },
        templateUrl: '../Templates/timeoptions.html'
    };
});

Directive Template:

Min: {{timeOptions.startTime}} Max: {{customerInfo.endTime}}

HTML:

<input type="text" placeholder="Start Time" id="timestart" pick-a-time options="timeRange" data-ng-model="itemtimestart" class="form-control" autocomplete="off">

Controller to pass in dynamic values (stored in REST)

// Query settings for variables to be used later in function
appSettings.query(function(settings) {
    // Data is within an object of "value", so this pushes the server side array into a variable
    var setting = settings.value;

    // Foreach result, where setting is equal to active get the apporpriate variables
    angular.forEach(setting, function(settingvalue, settingkey) {
        if (settingvalue.Title == 'Active') {

            // Get work and non work durations as variables
            workDuration = settingvalue.Work_x0020_Day_x0020_Hours_x0020;
            nonWorkDuration = settingvalue.Non_x0020_Work_x0020_Day_x0020_H;

            // Get $scope variables to control time picker range
            var startHour = settingvalue.Work_x0020_Day_x0020_Start_x0020.split(":")[0];
            var startMinute = settingvalue.Work_x0020_Day_x0020_Start_x0020.split(":")[1];
            var startTime = '[' + startHour + ',' + startMinute + ']';
            var endHour = settingvalue.Work_x0020_Day_x0020_End_x0020_M.split(":")[0];
            var endMinute = settingvalue.Work_x0020_Day_x0020_End_x0020_M.split(":")[1];    
            var endTime = '[' + endHour + ',' + endMinute + ']';
            $scope.timeRange = {min: startTime, max: endTime};                  
        }
    })
});

The non-dynamic method (which works) on an input is as follows:

<input type="text" placeholder="End Time" id="timeend" pick-a-time="{min: [0,0], max: [23,30]}" data-ng-model="itemtimeend"class="form-control" autocomplete="off">

Update: Working with Dave, I have adjusted to the following. It logs the timeRange correctly, but gives an undefined for the timeOptions in the directive

timeRange Log:

 time start is [8,30]time end is [17,0]

timeOption log:

 time options log undefined

Directive (timeOptions undefined on logging):

appDirectives.directive('pickATime', function() {
return {
    // Restrict it to be an attribute in this case
    restrict: 'A',
    // responsible for registering DOM listeners as well as updating the DOM
    link: function(scope, element, attrs) {
        element.pickatime(scope.pickATime);
        console.log("time options log" + scope.timeOptions);
    },
    scope: {
        timeOptions: '='

    },
    templateUrl: '../Templates/timeoptions.html'
};

});

Template:

min: {{timeOptions.min}}, max: {{timeOptions.max}}

Controller (logs out correctly):

// Get $scope variables to control time picker range
var startHour = settingvalue.Work_x0020_Day_x0020_Start_x0020.split(":")[0];
var startMinute = settingvalue.Work_x0020_Day_x0020_Start_x0020.split(":")[1];
var startTime = '[' + startHour + ',' + startMinute + ']';
var endHour = settingvalue.Work_x0020_Day_x0020_End_x0020_M.split(":")[0];
var endMinute = settingvalue.Work_x0020_Day_x0020_End_x0020_M.split(":")[1];
var endTime = '[' + endHour + ',' + endMinute + ']';
$scope.timeRange = {
    min: startTime,
    max: endTime
};

HTML:

<input type="text" placeholder="Start Time" id="timestart" pick-a-time timeOptions="timeRange" data-ng-model="itemtimestart" class="form-control" autocomplete="off">

Update - Picker Working... but Now Form is not Submitting Time Data per this Post: TimePicker directive won't submit time (undefined)**

Huge thanks to @dave-alperovich and @joe-enzminger for tireless help and great answers.

Controller:

// Get $scope variables to control time picker range
var startHour = settingvalue.Work_x0020_Day_x0020_Start_x0020.split(":")[0];
var startMinute = settingvalue.Work_x0020_Day_x0020_Start_x0020.split(":")[1];
var endHour = settingvalue.Work_x0020_Day_x0020_End_x0020_M.split(":")[0];
var endMinute = settingvalue.Work_x0020_Day_x0020_End_x0020_M.split(":")[1];
$scope.timeRange = {
min: [startHour,startMinute],
max: [endHour,endMinute]
};

Directive:

appDirectives.directive('pickATime', function() {
return {
    // Restrict it to be an attribute in this case
    restrict: 'A',
    // responsible for registering DOM listeners as well as updating the DOM
    link: function(scope, element, attrs) {
        element.pickatime(scope.options());
    },
    scope: {
        options: '&pickATime'
    },
};
});

Usage:

<input ng-if="timeRange" type="text" placeholder="Start Time" id="timestart" pick-a-time="timeRange" data-ng-model="itemtimestart" class="form-control" autocomplete="off">

2 Answers 2

3
+50

There are many different ways to pass data from a parent scope into a directive. You've chosen (correctly) the class of choices offered by isolated scopes, as indicated by the

scope: {
}

parameter of your directive. This means that the scope of your directive will not prototypically inherit from the parent scope.

The reason your first example doesn't work is that the line:

element.pickatime(scope.$eval(attrs.pickATime));

ends up passing undefined to the element.pickatime() method.

This is because what this line of code does is attempt to evaluate the expression (which is a string) contained in the value of the pick-a-time html attribute from your DOM against the directive's scope. The value of the pick-a-time attribute is an empty string in your first example.

When you use the "non dynamic" version, the value of the pick-a-time attribute is now the string

"{min: [0,0], max: [23,30]}"

Which is a valid angular expression and $eval will return an object with min and max properties set to [0,0] and [23,30] respectively, so your directive works.

In both cases, your directive completely ignores the value passed in to the directive with the options attribute.

An easy fix would be to modify your directive to use the information passed in via the options attribute. The way your directive is written, this object will be two-way bound to the $scope.timeOptions value (more on this later). Essentially, any changes to $scope.timeOptions will show up in the parent scope's timeRange property, and vice versa.

The change to your directive would be to use:

element.pickatime(scope.timeOptions);

Extra credit:

In your last example, you state that timeOptions is undefined in the directive. This is because the html needs to be:

<input type="text" placeholder="Start Time" id="timestart" pick-a-time time-options="timeRange" data-ng-model="itemtimestart" class="form-control" autocomplete="off">

The only change is timeOptions -> time-options

This is how angular handles attribute to scope property mapping by convention.

Extra Extra Credit:

Your example does not require two-way binding (I don't think you want to change the options inside the directive). You can avoid the creation of two $watches by using & instead of =. This would be my final version of your directive:

appDirectives.directive('pickATime', function() {
    return {
        // Restrict it to be an attribute in this case
        restrict: 'A',
        // responsible for registering DOM listeners as well as updating the DOM
        link: function(scope, element, attrs) {
            element.pickatime(scope.options());
        },
        scope: {
            options: '&pickATime'
        },
        templateUrl: '../Templates/timeoptions.html'
    };
});

with usage:

UPDATE for dot notation issue:

In your controller, you need to use dot notation for itemtimestart:

.controller('xxx', function($scope){
    $scope.parameters = {
    }
}

<input ng-if="timeRange" type="text" placeholder="Start Time" id="timestart" pick-a-time="timeRange" data-ng-model="parameters.itemtimestart" class="form-control" autocomplete="off">

$scope.paramters.itemtimestart should now be the correct time.

You can also simplify this by using the "controller as" syntax.

Here is a Plunk

Sign up to request clarification or add additional context in comments.

27 Comments

element.pickatime(scope.options()) I am thinking scope.options would be an object not a function.
@Scottux, actually, no. & bindings create a function that, when called, return the value returned by evaluating the expression (the string value of the html attribute) against the parent scope.
@Kode, this is probably because the input is being created before the timeRange information is retrieved from your server. You can verify by adding an ng-if. I've added this to my edited answer. You can also do this in the directive by using a $watch, but ng-if accomplishes the same thing.
@Kode, I think this implementation needs a function: $scope.timeRange = function() { return {min: startTime, max: endTime} }; and the pass thru would be pick-a-time="timeRange()" ... I didn't notice that we were over-writing the reference. You need a function that sends the "current values" every time the $digest cycle runs
When specify a controller on a dom element <div ng-controller="main as vm"></div>, it will add the controller instance to the scope as the property "vm".
|
0

If the options is already there ( in timeRange) and will not change over time, you have nothing to do: it should be in scope.timeOptions (both in your link and controller).

However if the options can change at any time and you want to refresh the composant when it does, you need to watch for its changes:

scope.$watch('timeOptions', function(newOptions) {
  // do something with the newOptions
});

6 Comments

So my code should be working? Since updates rarely occur? At this point, the timepicker is not reflecting the variables
What are you supposed to do with the options? If you just need to display them in your template, indeed this should be enough.
I need to use them as variables in the directive to control the time range.
You have them in scope.timeOptions, I don't know what to say more, do you have any specific question?
This is very confusing use case and many advanced users intuitively make this mistake, When you pass a variable name, it does NOT need to be evaluated. An object expression like {min: [0,0], max: [23,30]} does need to be evaluated. That's why OP had success with "hard coded values" -- notation, but failed due to the attempt to $eval a variable name.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.