Search code examples
jqueryangularjsevent-handlingcss-selectorssmart-table

Proper way to Watch/Update a group of inputs in AngularJS


I'm currently working on learning AngularJS after years of working with JQuery. Right now, I'm looking at implementing Smart Tables (http://lorenzofox3.github.io/smart-table-website/) and would like to be able to automatically save the search queries to a cookie so that I can reload them when a user would return. Ideally, I would like to have a generic way to do this so I can re-use the code by either using a single directive or DI.

In the JQuery world, I would do this:

HTML: Single search entry defined by st-search but we can have n-number of these all with unique id's.

<tr>
    <th><input st-search="id" class="form-control" placeholder="Search by ID" type="number"/></th>
</tr>

"onChange" Javascript: Listen to all the field updates so that, instead of alerting, I could call my storage service (probably a cookie) to save the updated search param for this table.

$('input[st-search]').on('change', function(event){
    var target = $(event.target);
    alert(target.attr('st-search')+'='+target.val());
});

Initialization Javascript: This would take the cookieData and apply it to all matching search fields.

$.each(cookieData, function(index, row) {
    $('input[st-search="'+row.stSearch+'"]').val(row.value);
});

Right now, I'm getting hung up on what the Angular way is to do this. I'm trying to follow the suggestion to try and do "everything" in Angular without JQuery ("Thinking in AngularJS" if I have a jQuery background?) but I'm not sure how to best approach this. Can you use $scope.$watch to generically watch a lot of elements based on CSS selector (I can only find examples to do it to a specific model)? Would this be best done with a decorator either on each input or the the parent element? I'm really not sure where to go from here!


Solution

  • Well, it's not a pretty solution but it does seam to do what I need by combining Angular with jQuery! I decided to go with an attribute/directive (st-search-cookie) on the tr tag that contains the search fields. For example:

    <tr st-search-cookie>
        <th><input st-search="id" class="form-control" placeholder="Search by ID" type="number"/></th>
    </tr>
    

    You can optionally define the cookie-name and collection-name on the directive. Otherwise, it uses the md5 hash of all the available search fields as the collection-name. The idea being that you have a single cookie that can have collections for many different tables instead of having lots of cookies (i.e. having to have one for every table).

    Anyway, this automatically finds the default st-search input boxes (input[st-search]) as well as any with the *predicate' attribute (input[predicate]) that are commonly used when making custom search directives.

    Hopefully someone else will find this useful or, even better, improve on it!

    (function(angular) {
        angular.module("stSearchCookie", ['ngCookies', 'angular-md5']).directive('stSearchCookie', function ($cookies, md5, $log) {
            return {
                restrict: 'A',
                scope: {
                    'cookieName': '@',
                    'collectionName': '@'
                },
                transclude: false,
                link: {
                    post: function (scope, elem, attrs, controller) {
                        // Set Defaults for scope
                        if (angular.isUndefined(scope.cookieName)) {
                            scope.cookieName = 'stSearchParams';
                            $log.log('cookie-name not defined so defaulting to "' + scope.cookieName + '"');
                        }
                        if (angular.isUndefined(scope.collectionName)) {
                            var fields = 'fieldNames=';
                            $.each(elem.find('input[st-search]'), function(i, e) {
                                fields = fields + $(e).attr('st-search') + ';';
                            });
                            $.each(elem.find('input[predicate]'), function (i, e) {
                                fields = fields + $(e).attr('predicate') + ';';
                            });
                            scope.collectionName = md5.createHash(fields);
                            $log.log('collection-name not defined so defaulting to "' + scope.collectionName + '"');
                        }
    
                        // Set defaults in cookie
                        var defaults = {};
                        $.each(elem.find('input[st-search]'), function(i, e) {
                            defaults[$(e).attr('st-search')] = '';
                        });
                        $.each(elem.find('input[predicate]'), function (i, e) {
                            defaults[$(e).attr('predicate')] = '';
                        });
                        var cookieData = $cookies.getObject(scope.cookieName);
                        if (angular.isObject(cookieData)) {
                            if (cookieData.hasOwnProperty(scope.collectionName))
                                cookieData[scope.collectionName] = angular.extend(defaults, cookieData[scope.collectionName]);
                            else
                                cookieData[scope.collectionName] = defaults;
                        } else {
                            cookieData = {};
                            cookieData[scope.collectionName] = defaults;
                        }
                        $cookies.putObject(scope.cookieName, cookieData);
    
                        // Populate from cookie
                        $.each(Object.keys(cookieData[scope.collectionName]), function (index, key) {
                            $('input[st-search="' + key + '"]').val(cookieData[scope.collectionName][key]).trigger('change').trigger('blur');
                        });
                        $.each(Object.keys(cookieData[scope.collectionName]), function (index, key) {
                            $('input[predicate="' + key + '"]').val(cookieData[scope.collectionName][key]).trigger('change').trigger('blur');
                        });
    
                        // Add events to update cookie on changes
                        elem.find('input[st-search]').on('change blur', function(event) {
                            var target = $(event.target);
                            var cookieData = $cookies.getObject(scope.cookieName);
                            cookieData[scope.collectionName][target.attr('st-search')] = target.val();
                            $cookies.putObject(scope.cookieName, cookieData);
                        });
                        elem.find('input[predicate]').on('change blur', function (event) {
                            var target = $(event.target);
                            var cookieData = $cookies.getObject(scope.cookieName);
                            cookieData[scope.collectionName][target.attr('predicate')] = target.val();
                            $cookies.putObject(scope.cookieName, cookieData);
                        });
                    }
                }
            };
        });
    })(angular);