Donnerstag, 5. Februar 2015

Finden von nicht verwendeten CSS Klassen

Die Fragestellung des Tages: Wie finde ich CSS-Klassen, die im HTML definiert wurden aber nie in Styles verwendet werden?
Die Antwort scheint "fast unmöglich" zu sein. Leider.
Ich habe mal versucht eine Näherung zu finden - der folgende code (angewendet auf eine beliebige Seite in der jQuery geladen ist) listet die im code definierten CSS-Klassen und gleicht diese anschießend mit den in den Styles definierten Selektoren ab:

(function($){
 var classesInUse = {
   _definedProperties: []
  },
  selectorsInUse = {
   _definedProperties: []
  };

 //
 // find all used classes
 //
 $('[class]').each(function(){
  var $this = $(this),
   className = this.className,
   classes = className.split(/\s+/);
  $.each(classes, function(_,c) {
   if(c) {
    //console.log('found a usage of class ' + c);
    if(!classesInUse[c]) {
     classesInUse[c] = {
      elems: [$this]
     };
     classesInUse._definedProperties.push(c);
    } else {
     classesInUse[c].elems.push($this);
    }
   }
  });
 });
 classesInUse._definedProperties.sort();

 $.each(classesInUse._definedProperties, function(_, p){
  console.log('class ' + p + ' was used ' + classesInUse[p].elems.length + ' times in HTML.');
 });
 
 //
 // find all defined css selectors
 // 
 $.each(document.styleSheets, function(_, style) {
  $.each(style.cssRules, function(_, rule) {
   var selector = rule.selectorText;
   if(selector) {
    //console.log('found a definition of css-selector ' + selector);
    if(!selectorsInUse[selector]) {
     selectorsInUse[selector] = {
      count: 1
     };
     selectorsInUse._definedProperties.push(selector);
    } else {
     selectorsInUse[selector].count += 1;
    }
   }
  });
 });
 selectorsInUse._definedProperties.sort();

 $.each(selectorsInUse._definedProperties, function(_, p){
  console.log('selector "' + p + '" was defined ' + selectorsInUse[p].count + ' times in CSS.');
 });
 
 
 //
 // compare classes & selectors
 //
 $.each(classesInUse._definedProperties, function(_, klass){
  var classSelector = '.' + klass,
   usages = 0;
  $.each(selectorsInUse._definedProperties, function(_, cssSelector){
   if(cssSelector.indexOf(classSelector) > -1) {
    usages += 1;
   }
  });
  classesInUse[klass].usagesInCss = usages;
  if(usages === 0) {
   console.log('class ' + klass + ' was not used in CSS!');
  } else {
   console.log('class ' + klass + ' was used in CSS ' + usages + ' times.');
  }
 });
})(jQuery);

Der Code hat natürlich noch diverse Nachteile:

  • Nur das aktuell geladene HTML im DOM wird nach Klassen durchsucht.
  • Nur das aktuell aktive CSS wird nach Selektoren durchsucht.
  • Klassen, die verwendet werden um Elemente einfach im HTML per jQuery auffindbar zu machen werden ggf. als "nicht verwendet" aufgelistet.
Die Ergebnisse sind also mit entsprechender Vorsicht zu verwenden!


Der umgekehrte Fall (Selektoren zu finden, die nicht verwendet werden) scheint übrigens gängiger zu sein: Dafür gibt es tools.

Dienstag, 3. Februar 2015

JSON mimeType in WebMatrix (IIS-Express)

Ich arbeite in letzter Zeit viel mit der WebMatrix.
Zu test-Zwecken verwende ich gerne ajax-Requeste, die lokal abgelegte json-Daten abrufen anstatt einer echten API.
Der Zugriff auf eine solche json-Datei schlägt (standardmäßig) fehl, mit der Meldung:
HTTP-Fehler 404.3 - Not Found
und der Erklärung:
Die angeforderte Seite kann aufgrund einer Konfigurationserweiterung nicht angezeigt werden. Wenn es sich bei der Seite um ein Skript handelt, müssen Sie einen Handler hinzufügen. Wenn die Datei heruntergeladen werden soll, müssen Sie eine MIME-Zuordnung hinzufügen.

Um die genannte MIME-Zuordnug dem Projekt hinzuzufügen erstellt man eine Web.config im Wurzelverzeichnis des Projektes mit folgendem Inhalt:

<?xml version="1.0"?>

<configuration>
    <system.webServer>
        <staticContent>
            <mimeMap fileExtension=".json" mimeType="application/json" />
        </staticContent>
    </system.webServer>
</configuration>

Anschließend werden vom IIS-Express (den die WebMatrix startet) auch json-Files korrekt ausgeliefert.

JavaScript Countdown mit jQuery

Folgende Fragestellung:
Wie kann man auf einer Seite mehrere Datums-Conuntdowns mit JavaScript realisieren, die unabhängig von einander laufen und gleichzeitig nicht komplett auf dem (möglicherweise falsch gesetztem) Client-Datum basieren?
Als Idee/Grundlage wurde http://trulycode.com/bytes/easy-countdown-to-date-with-javascript-jquery/ genannt.

Nach kurzer Prüfung habe ich den Code für unstrukturiert (und ungeeignete als Grundlage) befunden. Die Idee dies als jQuery-Plugin zu realisieren halte ich aber für gut.

Mein erster Wurf benötigt sicher noch diverse Überprüfungen und ist auch nicht 100% getestet aber ich denke auf jeden Fall schon besser als die "Vorlage".

(Angucken und ausprobieren bei Codepen!)

(function($){
  $.fn.countdown = function(from, to, config){
    var localDate = new Date(),
        loop = function() { 
          var currentDate = new Date(),
              localDiff = currentDate.getTime() - localDate.getTime(),
              param = {
                current: new Date(from),
                target: new Date(to),
              };
          param.current.setTime(param.current.getTime() + localDiff);
          param.millis = param.target.getTime() - param.current.getTime();
          param.days = Math.floor(param.millis / 86400000);
          param.millis -= param.days * 86400000;
          param.hours = Math.floor(param.millis / 3600000);
          param.millis -= param.hours * 3600000;
          param.minutes = Math.floor(param.millis / 60000);
          param.millis -= param.minutes * 60000;
          param.seconds = Math.floor(param.millis / 1000);
          param.millis -= param.seconds * 1000;
          
          // Abbruch oder weiter..
          if(param.current.getTime() >= param.target.getTime()) {
            // callback rufen und abbruch
            config.callback.call(this, param);
          } else {
            // text setzen und weiter
            config.printDate.call(this, param);
            window.setTimeout(loop.bind(this), config.timeout);
          }
    };
    // config sicher stellen
    if(!config) {
      config = {};
    }
    if(!config.printDate) {
      config.printDate = function(x) {
        $(this).text(x.days + "d " + x.hours + "h " + x.minutes + "m " + x.seconds+ "s " + x.millis);
      };
    }
    if(!config.callback) {
      config.callback=function(){};
    }
    if(!config.timeout) {
      config.timeout=1000;
    }
    loop.call(this);
    return this;
  };
})(jQuery);