Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

inheriting methods through directive expressions

I'm trying to create a reusable button directive which has a 'loading' state. That is, when clicked it disables itself and shows an inline image to denote loading, which it then removes when complete. I do this by setting a scope variable in the click method and unsetting it, which changes the state of the button.

I want the method it calls when clicked to be in the parent scope, and I also want it to hook into the validation of the parent scope, so it disables itself when the parent form is invalid. These are the parts i'm having difficulty getting to work - I know my issues are scope-related, but i'm stuck.

<loading-button class="login" data-ng-click="login()" text="Login" toggle="loaded"></loading-button>

I was hoping to do something like below, but how can I bind the click method declared on the directive instance to be actually called from the directive? Or, is this bad practice? This doesn't currently work.

angular.module("App.directives").directive("loadingButton", function () {
return {
    restrict: "E",
    replace: true,
    transclude: true,
    template: '<button data-ng-click="{{ngClick}}">{{text}}<img class="loading" src="images/ButtonLoader.gif" alt=""></button>',
    scope: {
        "toggle": "=",
        "text": "=",
        "ng-disabled": "=",
        "disabled": "=",
        "ngClick": "&"
    },
    link: function(scope, element, attributes) {
        scope.text = attributes.text;
        var expression = attributes.toggle;

        scope.$watch(expression, function(newValue, oldValue) {
            if(newValue === oldValue) {
                return;
            }

            if(newValue) {
                element.removeAttr("disabled");
                element.find("img.loading").hide();
            }
            else {
                element.attr("disabled", "disabled");
                element.find("img.loading").show();
            }
        });
    }
  };
});

Used like this in the parent scope:

$scope.login = function () {
    $scope.loaded = false;  // Disable button and show it loading

    // Do login stuff

    $scope.loaded = true; // Enable button and hide it loading
}

Edit:

Here's a fiddle

http://jsfiddle.net/jonathanwest/frvk6/2/

like image 847
jwest Avatar asked Nov 13 '22 01:11

jwest


1 Answers

EDIT - simplest solution: If all you are doing is enabling a button and hiding elements in that button, you don't need a directive at all:

<button ng-click="login()" ng-disabled="loading"><img ng-show="loading">Login</button>

OTHERWISE, if you want it in a directive:

For starters, you don't need {{}} in your click declaration in the template, and you need to call ().

I am changing the attribute reference from ngClick to click - angular won't like you using its names as your attributes:

<button data-ng-click="click()">

And use ng-show and ng-disabled for your button and image.

<button ng-click="click()" ng-disabled="text==\'loading\'">
    <img ng-show="text==\'loading\'">{{text}}</button>

.

For the text attribute, since you are just reading the value of the string, you will want to change that scope setting to text: "@".

Also, since toggle is being set in your isolate scope, you can just $watch that directly for changes.

*Note this is just an example of disabling and showing elements based on a scope $watch. Your disabling and showing may need the exact opposite values of the ones below, or may be dependent on a different scope variable:

scope.$watch('toggle', function(newValue, oldValue) {
            if(newValue === oldValue) {
                return;
            }

            if(newValue) {
                scope.text = "loaded";
               ;
            }
            else {
                scope.text="loading";

            }
        });

.

This fiddle reflects a working example based on your setup, with simulated loading time using a $timeout.

like image 189
rGil Avatar answered Nov 15 '22 00:11

rGil