0

I'm quite new to AngularJS so I try to understand it. Thus, don't shoot me when you read the initial question.

I'm developping an application using AngularJS. The HTML looks somehow like this:

<div id="OfficeUI" ng-controller="Office as Office">

    <div class="absolute">
        <div class="container application-icons icon">
            <img id="{{icon.Id}}" ng-repeat-start="icon in Office.Icons" ng-repeat-end ng-src="{{icon.Icon}}" alt="{{icon.Alt}}" />
        </div>
    </div>

</div>

The controller of my application looks like:

var OfficeUI = angular.module('Office');

// Defines the Office controller for the application.
OfficeUI.controller('Office', ['$http', function($http) {
    // Defines required variables.
    var application = this;

    // Get the Json file 'application.json' that defines the application data.
    $http.get('/OfficeUI.Beta/Resources/JSon/application.json')
        .success(function(data) {
            application.Title = data.Title;
            application.Icons = data.Icons;
        })
        .error(function(data) {
            console.error('An error occured while loading the \'application.json\' file.');
        });
}]);

As you see in the HTML, I'm binding image elements based on the data which is found in a particular JSon file.

Now, I want the user to give the possibility to add an event to a given element (in this case an image). Herefore, I've developped a jQuery plugin that looks like:

(function ( $ ) {
    $.fn.OfficeUI = function(options) {

        var settings = $.extend({
        }, $.fn.OfficeUI.Defaults, options);

        return this;
    }

    $.fn.OfficeUI.Defaults = { };

    $.fn.OfficeUI.bind = function(elementSelector, bound, action) {
        $(elementSelector).on(bound, function() { action(); });

        return this;
    };
}(jQuery));

This plugin allows me to call add an event as following:

$(this).OfficeUI.bind("icoApplication", "click", function() {
    console.log('The following element is bound.');
});

However, the first parameter of the function takes a selector to find the element to attach the event to. But in AngularJS this isn't working since the elements are not created yet.

Anyone who has an idea on how to resolve this issue? I do want to provide a clean, minimalistic way for users to bind events to the application, without modifying existing controllers, directives, filters, ...

Thanks in advance.

Complexity
  • 5,682
  • 6
  • 41
  • 84
  • i would suggest looking at the documentation for `angular.element()` (which is jquery if you load jquery - jQLite if not) and suggest that if you do the binding inside the scope of the controller, you could then control that it happens in a $digest cycle [see this](https://docs.angularjs.org/api/ng/type/$rootScope.Scope) which allows angular to be aware of what you are doing and create before binding - in general though, if you are going to use angular, avoid building things in jQuery and use directives or services in angular instead. – tophallen Jan 08 '15 at 07:55

3 Answers3

0

I'm not sure if I do understand your question correct. Why won't you use a ng-click on the image?

<div id="OfficeUI" ng-controller="Office as Office">

<div class="absolute">
    <div class="container application-icons icon">
        <img ng-click="myFunction() id="{{icon.Id}}" ng-repeat-start="icon in Office.Icons" ng-repeat-end ng-src="{{icon.Icon}}" alt="{{icon.Alt}}" />
    </div>
</div>

In the controller you write:

// Defines the Office controller for the application.
OfficeUI.controller('Office', ['$scope','$http', function($scope, $http) {
    // Get the Json file 'application.json' that defines the application data.
    $http.get('/OfficeUI.Beta/Resources/JSon/application.json')
        .success(function(data) {
            $scope.Title = data.Title;
            $scope.Icons = data.Icons;
        })
        .error(function(data) {
            console.error('An error occured while loading the \'application.json\' file.');
        });

    $scope.myFunction = function () {
        console.log('The following element is bound.');
    }
}]);

And you bind the view to the controller:

<div id="OfficeUI" ng-controller="Office">

<div class="absolute">
    <div class="container application-icons icon">
        <img id="{{icon.Id}}" ng-repeat-start="icon in Icons" ng-repeat-end ng-src="{{icon.Icon}}" alt="{{icon.Alt}}" />
    </div>
</div>

Stefan van de Vooren
  • 2,524
  • 22
  • 19
  • Because that's what I want to avoid. How would I then say that the first image should respond on a click and the second one should respond on a hover, the third on a scroll event, ... – Complexity Jan 08 '15 at 08:09
  • You may add an Angular if inside the ng-repeat? http://stackoverflow.com/questions/21751676/using-ng-if-inside-ng-repeat. Or add params to myFunction()? – Stefan van de Vooren Jan 08 '15 at 08:45
  • I don't see how that will solve my problem. I want users to be able to add events to angularJS rendered elements without modifying controllers, directives or any other items. – Complexity Jan 08 '15 at 08:49
  • My project is built up using another approach. I give the user the ability to specify the amount of actions and their id in a JSON file. Using your approach, my customer needs to modify the HTML (template) to implement custom functions, which is something I would like to avoid at all times. I don't want the customer to mess with those files. It should be at some other place. – Complexity Jan 08 '15 at 10:17
0

The proper way would be using angular directives have a look at this SO question

or this example

Example

App.directive('directiveName', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            $(element).'pluginActivationFunction'(scope.$eval(attrs.directiveName));
        }
    };
}); 
Community
  • 1
  • 1
Merlin
  • 4,907
  • 2
  • 33
  • 51
  • I know the aspect of directives, but when you add a directive on the img element in the loop, the directive is added to all the elements. What if I want a click on the first and a hover on the first element? – Complexity Jan 08 '15 at 08:08
0

After some hours of searching, I've found a solution which I'm very happy with.

So, let's sum up what I do have for the moment:

I do have JSon file that's used to render the website which looks like the following:

{
  "Title": "Inbox - user@github.com - Outlook",
  "Icons": [
    {
      "Id": "icoApplication",
      "Icon": "Resources/Images/Application/Application.png",
      "Alt": "Outlook"
    },
    {
      "Id": "icoSendReceive",
      "Icon": "Resources/Images/Application/Send-Receive.png",
      "Alt": "Send / Receive"
    },
    {
      "Id": "icoUndo",
      "Icon": "Resources/Images/Application/Undo.png",
      "Alt": "Undo"
    }
  ]
}

Then, I do have the HTML which is being rendered used AngularJS:

<div class="absolute">
    <div class="container application-icons icon">
        <img id="{{icon.Id}}" ng-repeat-start="icon in Office.Icons" ng-repeat-end ng-src="{{icon.Icon}}" alt="{{icon.Alt}}" />
    </div>
</div>

Now, I do have customers who can use this AngularJS application. I would like to give the ability to the users to attach any event (click, scroll, mouseenter, ...) on any img element.

The customer can add elements in the JSon file, but I want to avoid that my customers needs to modify the AngularJS template in order to attach events, because that would mean that the customer needs to write conditions and other stuff in the template. I would stick with plain jQuery / JavaScript for attaching events.

So, what I've done is created a custom AngularJS directive that allows me to attach events. This directive looks like the following:

OfficeUI.directive('ngcDynamicEventHandling', function() {
    return {
        restrict: 'A',
        replace: 'true', 
        link: function(scope, element, attributes) {
            var parameter = scope.$eval(attributes['ngcDynamicEventHandling']); // Get the     parameters passed to this directive.

            // Check if an event is attached to this object.
            var registeredEvent = $(this).OfficeUI.searchEvent(parameter);

            // An attached event has been found, so let's register it.
            if (registeredEvent != null) {
                element.on(registeredEvent.handler, function() { 
                    registeredEvent.action();
                });
            }
        }
    };
});

The above directive accepts a parameter which I'm retrieving in this directive. Then, I do call a jQuery plugin to attach my events.

The HTML when adding the directive does look like the one below:

<div class="absolute">
    <div class="container application-icons icon">
        <img ngc-dynamic-event-handling="icon.Id" id="{{icon.Id}}" ng-repeat-start="icon in Office.Icons" ng-repeat-end ng-src="{{icon.Icon}}" alt="{{icon.Alt}}" />
    </div>
</div>

Now, let's move on to my jQuery plugin that allows me to register events.

(function ( $ ) {
    $.fn.OfficeUI = function(options) {

        var settings = $.extend({
        }, $.fn.OfficeUI.Defaults, options);

        return this;
    }

    $.fn.OfficeUI.Defaults = { };

    var eventCollection = []; // Defines a private collection of registered events.

    $.fn.OfficeUI.bind = function(element, handler, action) {
        eventCollection.push({
            element: element,
            handler: handler,
            action: action
        });
    };

    $.fn.OfficeUI.searchEvent = function(element) {
        var foundElement = $.grep(eventCollection, function(item) {
            return item.element == '#' + element
        });

        if (foundElement.length != 0) {        
            return foundElement[0];
        } 

        return null;
    };
}(jQuery));

So, the first function which is important is the bind function. This function can be called like:

$(this).OfficeUI.bind("#icoApplication", "click", function() {
    console.log('This method is executed when you click on the application icon.');
});

By calling the above function, I add an object with certain properties into an array.

$.fn.OfficeUI.bind = function(element, handler, action) {
    eventCollection.push({
        element: element,
        handler: handler,
        action: action
    });
};

Now, let's check our directive again:

var parameter = scope.$eval(attributes['ngcDynamicEventHandling']); // Get the parameters passed to this directive.

// Check if an event is attached to this object.
var registeredEvent = $(this).OfficeUI.searchEvent(parameter);

// An attached event has been found, so let's register it.
if (registeredEvent != null) {
    element.on(registeredEvent.handler, function() { 
        registeredEvent.action();
    });
}

What I'm doing here is executing the function searchEvent of my plugin, which will check if I've registered an event to the element on which the directive is placed.

If that's the case, that event is registered to the element.

So, this was a fairly long post, but I hope I've made something clear. Right now, I do have the ability to attach events to AngularJS rendered HTML, and I don't need to modify the AngularJS templates. And what's even more, I can attach all kinds of elements, not only click or something else.

This give some great flexibility to end-users.

If someone has thoughts on this, don't hesitate to reply.

Complexity
  • 5,682
  • 6
  • 41
  • 84