Trolliminator!

I am a big soccer fan, and I love my MLS. I frequent the site and read up on all the articles. Unfortunately, there are a few faces that are always trolling in the comments. I really don’t even care to read what they have to say, as it doesn’t contribute to the discussion. I decided I’d do something about it.

Trolliminator!

Introducing the Trolliminator! bookmarklet.

javascript: (function () {
  var trolls = [
		'troll', // <-- add troll names here
	];
 
    var el = document.createElement('div'),
        b = document.getElementsByTagName('body')[0];
    otherlib = false, msg = '';
    el.style.position = 'fixed';
    el.style.height = '32px';
    el.style.width = '220px';
    el.style.marginLeft = '-110px';
    el.style.top = '0';
    el.style.left = '50%';
    el.style.padding = '5px 10px 5px 10px';
    el.style.zIndex = 1001;
    el.style.fontSize = '12px';
    el.style.color = '#222';
    el.style.backgroundColor = '#f99';
    if (typeof jQuery != 'undefined') {
        msg = 'This page already using jQuery v' + jQuery.fn.jquery;
        return showMsg();
    } else if (typeof $ == 'function') {
        otherlib = true;
    }
    function getScript(url, success) {
        var script = document.createElement('script');
        script.src = url;
        var head = document.getElementsByTagName('head')[0],
            done = false;
        script.onload = script.onreadystatechange = function () {
            if (!done && (!this.readyState || this.readyState == 'loaded' 
|| this.readyState == 'complete')) {
                done = true;
                success();
            }
        };
        head.appendChild(script);
    }
    getScript('http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js', 
function () {
        if (typeof jQuery == 'undefined') {
            msg = 'Sorry, but jQuery wasn\'t able to load';
        } else {
            msg = 'This page is now jQuerified with v' + jQuery.fn.jquery;
            if (otherlib) {
                msg += ' and noConflict(). Use $jq(), not $().';
            }
        }
        return showMsg();
    });
 
    function showMsg() {
        el.innerHTML = msg;
        b.appendChild(el);
        window.setTimeout(function () {
            if (typeof jQuery == 'undefined') {
                b.removeChild(el);
            } else {
                jQuery(el).fadeOut('slow', function () {
                    jQuery(this).remove();
                });
                if (otherlib) {
                    $jq = jQuery.noConflict();
                }
                trolliminator();
            }
        }, 2500);
    }
 
    function trolliminator() {
    	var $ = jQuery;
    	var matchCounter = 0;
    	$.each(trolls, function(index, value) {
    		$('.gig-comments-comment-username').filter(function() {
    			var match = $(this).text() == value;
    			if (match) {
    				var table = $(this).closest('table');
    				table.hide();
    				table.parent().append(
$('<div>').css({ 'color':'#666', 'font-style':'italic' })
.html('Trolliminated!'));
    				matchCounter++;
    				return match;
    			}
    		});
    	});
    	$('<div>').css({ 
'position':'fixed', 'right':'0', 'bottom':'0', 
'padding':'5px', 'font-weight':'bold', 
'color':'#090', 'z-index':'99999' })
.html(matchCounter + ' trolls eliminated').appendTo('body');
    }
})();

To use it:

  1. Edit the “trolls” using comma-separated, single-quoted names
  2. Save it as a bookmarklet
  3. Hit it up prior to reading any article

It removes posts for matching “trolls” and replaces them with a “Trolliminated!” message. It also puts a counter down in the bottom-right corner. I started to develop a Chrome extension, but I ran into a little trouble and, to be honest, I did not want to put much effort into blocking the trolls – that is just another way they are fed.
🙂

MLSSoccer.com uses Gigya, so this should technically work with any other site that uses Gigya comments.

Recent gists

I have saved a couple of code snippets lately and just wanted to add them to the blog too. This one enables “focus out” unobtrusive validation. I use this in a couple of forms on the site. Part of this gist also has a little code where I was messing around with “remote” validation to get it to work with the focus out validation – of course, I wasn’t thinking, just geeking out. The remote stuff is already baked in to jquery validation and I already had code to handle the focus out portion, so it wasn’t really worth the time, but I didn’t spend much on it. Anyway, the code:

window.Em = window.Em || {};

Em = {
    setFocusOutValidation: function (form) {
        var s = $.data(form, "validator").settings;
        s.onfocusout = function (element) {
            if ($(element).val().length > 0) {
                $(element).valid();
            }
        };
        s.showErrors = function (map, list) {
            this.defaultShowErrors();
            if (list && list.length > 0) {
                for (prop in map) {
                    $("#" + prop).focus();
                }
            }
            Em.displayValidationSummaryErrors(list);
        };
        return s;
    },
    displayValidationSummaryErrors: function (list) {
        $("[data-valmsg-summary]")
            .removeClass("validation-summary-valid")
            .addClass("validation-summary-errors");
        $("[data-valmsg-summary] ul").html("");
        $.each(list, function (idx, data) {
            $("[data-valmsg-summary] ul").append($("<li></li>")
                .html(data.message));
        });
    }
}

The focus out validation will check if the field is valid (also remotely) and if not, the focus will stay on the invalid field. Sometimes it can get a little wonky, but for the most part it works well. Typically when it doesn’t seem to “work” it is because the validation is setup backwards, like comparing a “new password” to the “confirm password”, rather than the other way around.

Gist

The next gist is a Telerik MVC grid extension I started messing with to handle filtering. Turns out I was making it harder than necessary, and there was already an extension method in the NopCommerce codebase. Oh well, here it is anyway:

public static IList<T> ApplyFilter<T>(this IList<T> list, 
                                      FilterDescriptor filter) {
 
    // We wont allow filtering on anything but string type properties
    if (filter.MemberType != typeof(string)) 
        throw new ArgumentException(@"Filtering is only allowed 
            for properties with a type of 'string'.");
 
    var value = filter.Value.ToString();
    switch (filter.Operator) {
        case FilterOperator.IsEqualTo:
            list = list.Where(x => {
                var propertyValue = x.GetType()
                    .GetProperty(filter.Member)
                    .GetValue(x, null);
                return String.Equals((string)propertyValue, value, 
                    StringComparison.InvariantCultureIgnoreCase);
            }).ToList();
            break;
 
        case FilterOperator.IsNotEqualTo:
            list = list.Where(x => {
                var propertyValue = x.GetType()
                    .GetProperty(filter.Member).GetValue(x, null);
                return String.Equals((string)propertyValue, value, 
                    StringComparison.InvariantCultureIgnoreCase) == false;
            }).ToList();
            break;
 
        case FilterOperator.StartsWith:
            list = list.Where(x => {
                var propertyValue = x.GetType()
                    .GetProperty(filter.Member).GetValue(x, null);
                return ((string)propertyValue).StartsWith(value, 
                    StringComparison.InvariantCultureIgnoreCase);
            }).ToList();
            break;
 
        case FilterOperator.Contains:
            list = list.Where(x => {
                var propertyValue = x.GetType()
                    .GetProperty(filter.Member).GetValue(x, null);
                return ((string)propertyValue).IndexOf(value, 
                    StringComparison.InvariantCultureIgnoreCase) > -1;
            }).ToList();
            break;
 
        case FilterOperator.EndsWith:
            list = list.Where(x => {
                var propertyValue = x.GetType()
                    .GetProperty(filter.Member).GetValue(x, null);
                return ((string)propertyValue).EndsWith(value, 
                    StringComparison.InvariantCultureIgnoreCase);
            }).ToList();
            break;
    }
 
    return list;
}

Gist

The last one is just a little quickie example of how to select data attributes with jQuery:

var d = $("input[data-toggle-class]");
$.each(d, function (idx, data) {
    console.log(idx, data);
});

Gist

I was originally looking for a way to select a wildcard in the data attribute, but I couldn’t get it working. Anyway, this works fine – though I did not actually end up using it. 🙂

FluentValidation NotEqual client-side validation

I wanted client-side validation for NotEqual. I started out writing my own and got 90% of the way there, then I got a little stuck. A quick Google revealed an existing Gist – however, the original implementation was only for NotEqual property comparison and I need a value compariosn. So, I forked it and made it work for my needs.

(function ($) {
    $.validator.addMethod("notequal", function (value, element, param) {
        if (param.indexOf("#") == -1) return value != param;
        return value != $(param).val();
    }, $.validator.messages.notequal);

    $.validator.unobtrusive.adapters.add("notequal", ["field"], 
    function (options) {
        options.rules["notequal"] = options.params.field;
        if (options.message) options.messages["notequal"] = options.message;
    });
})(jQuery);
FluentValidationModelValidatorProvider.Configure(provider =>
{
    provider.Add(typeof(NotEqualValidator), 
        (metadata, context, description, validator) => 
        new NotEqualClientRule(metadata, context, description, validator));
});
@model Test.Models.PersonModel
@using (Html.BeginForm())
{
    @Html.TextBoxFor(x => x.First)
    @Html.ValidationMessageFor(x => x.First)
    @Html.TextBoxFor(x => x.Last)
    @Html.ValidationMessageFor(x => x.Last)
    <button type="submit">OK</button>
}
[Validator(typeof(PersonValidator))]
public class PersonModel
{
    public string First { get; set; }
    public string Last { get; set; }
}
public class PersonValidator : AbstractValidator<PersonModel>
{
    public PersonValidator()
    {
        RuleFor(x => x.First).NotEqual(x => x.Last);
    }
}

Gist

When I look back at it, the whole hash check bit in the js just feels icky, so I think I’d go back and fix it up – probably just check to see if the jQuery selector exists, and if so compare .val() otherwise just compare the value. Whatever, it works for now even if it isn’t perfect…