Montag, 12. Oktober 2015

SyntaxHighlighter im SharePoint

In diesem Bog verwende ich den Syntax Highlighter von Alex Gorbatchev​ - ich finde Code sieht dann gut aus.

Im SharePoint habe ich den bisher immer vermisst. Das hat nun ein ende: Ich habe eine Sandbox-Solution​ erstellt, die die Dateien des Syntax Highlighter im SharePoint (auf SiteCollectioin-Ebene) bereitstellt und zusätzlich ein Feature (auf Web-Ebene) dass jede angezeigte Seite nach Syntax Highlighter-Formatierungen durchsucht und die entsprechenden Dateien dynamisch lädt.

Dafür habe ich das folgende js-File verwendet:

if (!!window.Type) {
    //this is 'normal'
    Type.registerNamespace('SpSyntaxHighlighter');
} else {
    //this happend on i.e. EditBlog.aspx
    window.SpSyntaxHighlighter = window.SpSyntaxHighlighter || {};
}
SpSyntaxHighlighter.shAutoConfig = [[ 'applescript' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushAppleScript.js'],
[ 'actionscript3', 'as3' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushAS3.js'],
[ 'bash', 'shell' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushBash.js'],
[ 'coldfusion','cf' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushColdFusion.js'],
[ 'cpp', 'c' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushCpp.js'],
[ 'c#', 'c-sharp', 'csharp' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushCSharp.js'],
[ 'css' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushCss.js'],
[ 'delphi', 'pascal', 'pas' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushDelphi.js'],
[ 'diff', 'patch' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushDiff.js'],
[ 'erl', 'erlang' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushErlang.js'],
[ 'groovy' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushGroovy.js'],
[ 'java' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushJava.js'],
[ 'jfx', 'javafx' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushJavaFX.js'],
[ 'js', 'jscript', 'javascript' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushJScript.js'],
[ 'perl', 'Perl', 'pl' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushPerl.js'],
[ 'php' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushPhp.js'],
[ 'text', 'plain' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushPlain.js'],
[ 'powershell', 'ps' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushPowerShell.js'],
[ 'py', 'python' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushPython.js'],
[ 'ruby', 'rails', 'ror', 'rb' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushRuby.js'],
[ 'sass', 'scss' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushSass.js'],
[ 'scala' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushScala.js'],
[ 'sql' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushSql.js'],
[ 'vb', 'vbnet' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushVb.js'],
[ 'xml', 'xhtml', 'xslt', 'html' , '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/shBrushXml.js']];
SpSyntaxHighlighter.canApplySyntaxHighlighter = function () {
    /// Check, if SyntaxHighlighter should be applied. I.e. returns false, if something is in edit mode..
    /// fals, if loading of SyntaxHighlighter should be disabled
    var rteFields = document.getElementsByClassName('ms-rtefield');
    // check if page is in edit-mode or edit-form or something..
    // do not (!) modify source in an editor - because that will lead to the modification being saved...
    if (rteFields.length > 0) {
        SP.UI.Notify.addNotification('Edit-Mode detected. SyntaxHighlighter disabled.', false);
        return false;
    }
    return true;
};
SpSyntaxHighlighter.addEvent = function (elm, evt, func) {
    if (elm.addEventListener) {
        elm.addEventListener(evt, func);
    } else if (elm.attachEvent) {
        elm.attachEvent('on' + evt, func);
    } else {
        elm['on' + evt] = func;
    }
};
SpSyntaxHighlighter.removeEvent = function (elm, evt, func) {
    if (elm.removeEventListener) {
        elm.removeEventListener(evt, func);
    } else if (elm.detachEvent) {
        elm.detachEvent('on' + evt, func);
    } else {
        elm['on' + evt] = null;
    }
};
SpSyntaxHighlighter.loadScript = function (url, successHandler, errorHandler) {
    var script = document.createElement('script'),
        loaded, error;
    script.src = url;
    script.type = 'text/javascript';
    script.language = 'javascript';
    loaded = function () {
        SpSyntaxHighlighter.removeEvent(script, 'load', loaded);
        SpSyntaxHighlighter.removeEvent(script, 'error', error);
        if (successHandler) {
            successHandler(script);
        }
    };
    error = function () {
        SpSyntaxHighlighter.removeEvent(script, 'load', loaded);
        SpSyntaxHighlighter.removeEvent(script, 'error', error);
        if (errorHandler) {
            errorHandler();
        }
    };
 
    SpSyntaxHighlighter.addEvent(script, 'load', loaded);
    SpSyntaxHighlighter.addEvent(script, 'error', error);
    document.body.appendChild(script);
};
SpSyntaxHighlighter.loadCss = function (url) {
    var link = document.createElement('link');
    link.href = url;
    link.type = 'text/css';
    link.rel = 'stylesheet';
    document.head.appendChild(link);
};
SpSyntaxHighlighter.loadBrushes = function(success) {
    // replaces shAutoloader.js - I dont like the way it works...
    var oldHighlight = SyntaxHighlighter.highlight,
        highlightCalled = false,
        elements = SyntaxHighlighter.findElements(),
        processes = [{
            id: 'loadBrushes',
            done: false
        }],
        chechAllDone = function () {
            var done = true,
                i;
            for (i = 0; i < processes.length; i += 1) {
                if (!(processes[i].done)) {
                    done = false;
                    break;
                }
            }
             
            if(done){
                // everything is done. finish here...
                SyntaxHighlighter.highlight = oldHighlight;
                if(highlightCalled !== false) {
                    SyntaxHighlighter.highlight.call(SyntaxHighlighter, highlightCalled);
                }
                if (!!success) {
                    success();
                }
            }
        },
        buildBrushes = function(){
            var cfg = SpSyntaxHighlighter.shAutoConfig,
                brushes = {},
                cfgLine,
                url,
                i,j;
            for(i=0; i< cfg.length; i+=1) {
                cfgLine = cfg[i];
                url = cfgLine[cfgLine.length-1];
                for(j=(cfgLine.length-2); j >= 0; j -=1) {
                    brushes[cfgLine[j]] = url;
                }
            }
            return brushes;
        },
        brushes = buildBrushes(),
        processing = function(id) {
            var i;
            for(i=0; i < processes.length; i+=1) {
                if(id === processes[i].id) {
                    return processes[i];
                }
            }
            return false;
        },
        i,
        alias,      
        url;
    SyntaxHighlighter.highlight = function (parm) {
        // dont call highlight while Im searching
        highlightCalled = parm;
    };
    // now process the elements..
    for(i=0; i < elements.length; i+=1) {
        alias = elements[i].params.brush;
        url = brushes[alias];
         
        if(!url) {
            if(console && console.log){
                console.log('no brush defined for alias: '+ alias);
            }
            continue;
        }
         
        if(!!processing(url)){
            continue;
        }
 
        processes.push({
            id: url,
            done: false
        });
 
        if (!!window.SyntaxHighlighter.vars.discoveredBrushes) {
            if (!!window.SyntaxHighlighter.vars.discoveredBrushes[alias]) {
                // this brush was already loaded & discovered by SyntaxHighlighter. Theres nothing more to do...
                processing(url).done = true;
                continue;
            }
        }
         
        (function() {
            var urlClosure = url;
            SpSyntaxHighlighter.loadScript(urlClosure, function () {
                processing(urlClosure).done = true;
                chechAllDone();
            });
        })();
    }
     
    // finished..
    processing('loadBrushes').done = true;
    chechAllDone();
};  
SpSyntaxHighlighter.runSyntaxHighlighter = function () {
    /// 
    ///     Re-Run SyntaxHighlighter, make sure SyntaxHighlighter was loaded before calling.
    ///     i.e. call loadSyntaxHighlighter first.
    ///     this functions does nothing, if an RichTextEditor is opened in the page!
    /// 
 
    if (!SpSyntaxHighlighter.canApplySyntaxHighlighter()) return;
 
    SpSyntaxHighlighter.loadBrushes(function () {
        SyntaxHighlighter.vars.discoveredBrushes = null; // let SyntaxHighlighter re-run brush-discovery
        window.setTimeout(SyntaxHighlighter.highlight.bind(SyntaxHighlighter), 50);
    });
};
SpSyntaxHighlighter.loadSyntaxHighlighter = function () {
    var csspath = '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/styles/',
        scriptpath = '~siteCollection/SiteAssets/SpSyntaxHighlighter/syntaxhighlighter/scripts/',
        shTheme = 'shThemeDefault.css', //Possible Themes: shThemeDefault.css, shThemeDjango.css, shThemeEclipse.css, shThemeEmacs.css, shThemeFadeToGrey.css, shThemeMDUltra.css, shThemeMidnight.css, shThemeRDark.css
        loadSyntaxHighlighter = function () {
            // add SyntaxHighlighter, styles and autoloader, then run autoloader
            SpSyntaxHighlighter.loadCss(csspath + 'shCore.css');
            SpSyntaxHighlighter.loadCss(csspath + shTheme);
            SpSyntaxHighlighter.loadScript(scriptpath + 'shCore.js', function () {
                SpSyntaxHighlighter.SyntaxHighlighter = window.SyntaxHighlighter;
                SpSyntaxHighlighter.runSyntaxHighlighter();
            });
        },
        siteLoadFialed = function (err) {
            if (console && console.log) {
                console.log('accessing siteCollecion failed. ');
                console.log('err was: ' + err);
                console.log('execution aborted. :-(');
            }
            // this is the end of it. sadly.
        },
        siteLoadSucceded = function () {
            var siteCollectionUrl = siteCollection.get_url(),
                i, len, brushCfg, brushPath;
            // fix up paths...
            csspath = csspath.replace('~siteCollection', siteCollectionUrl);
            scriptpath = scriptpath.replace('~siteCollection', siteCollectionUrl);
 
            len = SpSyntaxHighlighter.shAutoConfig.length;
            for (i = 0; i < len; i += 1) {
                brushCfg = SpSyntaxHighlighter.shAutoConfig[i];
                brushPath = brushCfg[brushCfg.length - 1];
                brushCfg[brushCfg.length - 1] = brushPath.replace('~siteCollection', siteCollectionUrl);
            }
 
            // now start loading SyntaxHighlighter
            loadSyntaxHighlighter();
        },
        siteCollection,
        clientContext = SP.ClientContext.get_current(),
        highlightable = document.getElementsByTagName('pre');
 
    // check if syntaxHighlighter ist needed
    if (highlightable.length < 1) return;
 
    if (!SpSyntaxHighlighter.canApplySyntaxHighlighter()) return;
 
    // load siteCollectionData then continue
    siteCollection = clientContext.get_site();
    clientContext.load(siteCollection);
    clientContext.executeQueryAsync(siteLoadSucceded, siteLoadFialed);
};
SpSyntaxHighlighter.preLoadSyntaxHighlighter = function () {
    if (!!SpSyntaxHighlighter.SyntaxHighlighter) {
        //weve been here before :-)
        window.SyntaxHighlighter = SpSyntaxHighlighter.SyntaxHighlighter;
        SpSyntaxHighlighter.runSyntaxHighlighter();
        return;
    }
    // load all from scratch...
    SP.SOD.executeFunc('sp.js', 'SP.ClientContext', SpSyntaxHighlighter.loadSyntaxHighlighter);
};
SpSyntaxHighlighter.register = function () {
    var thisUrl;
    SpSyntaxHighlighter.preLoadSyntaxHighlighter();
    if (!!window._spPageContextInfo) {
        thisUrl = '~siteCollection/SiteAssets/SpSyntaxHighlighter/ShLoader.js'.replace('~siteCollection/', window._spPageContextInfo.siteServerRelativeUrl);
        window.RegisterModuleInit(thisUrl, SpSyntaxHighlighter.preLoadSyntaxHighlighter);
    }
};
 
//Simulate LoadAfterUi=true
window._spBodyOnLoadFunctionNames.push('SpSyntaxHighlighter.register');

Teile des Skriptes (z.B. die Verfügbaren "brushes") lasse ich dabei auto-generieren mittels eines T4-Templates.

Die Sourcen für das ganze habe ich auf bitbucket​.

Montag, 31. August 2015

SharePoint liefert debug-JS

Das im SharePoint verwendete JavaScript ist minified, aber manchmal möchte ich es trotzdem debuggen...

Das geht über eine Einstellung in der web.config oder über das ScriptMode-Attribut am ScriptManager in der MasterPage.

Ein Attribut in der web.config scheint mir einfacher als die MasterPage anzupassen... Mit der Einschränkung, dass eine manuelle Anpassung der web.config im SharePoint keine wirklich gute Idee ist. Microsoft hat uns dafür die SPWebConfigModification mitgegeben.

Damit das ein- und abschalten der debugging-JS trotzdem einfach ist habe ich mir das folgende Skript gebaut:

<#
    .SYNOPSIS
    Set your SharePoint webApplication to use debugging JS-files 
    instead of minified.

    .DESCRIPTION
    SharePoint uses minified JavaScript (e.g. SP.js) but is also able to
    use debugging versions of the files instead. (e.g. SP.debug.js)
    Setting SharePoint to deliver debug-JS requires a setting
    in web.config. 
    This script sets the required value by adding a SpWebConfigModifaction
    to your WebApplication.

    .PARAMETER WebApplication
    URL to WebApplication

 .PARAMETER Remove
    Remove this modification (and thereby restore the original web.config)

    .EXAMPLE
    .\Enable-SpJsDebugging.ps1 -WebApplication http:/your.farm/site/
    Apply webConfig-Modification to allow for debugging-js to be used.

    .EXAMPLE
    .\Enable-SpJsDebugging.ps1 -WebApplication http:/your.farm/site/ -Remove
    Remove webConfig-Modification and retore original web.config
#>
[CmdletBinding()]
param (
 [Parameter(Mandatory=$true)]
    [string]$WebApplication,

    [Parameter(Mandatory=$false)]
    [switch]$Remove = $false
)

if ( (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null )
{
 Add-PSSnapin Microsoft.SharePoint.PowerShell
}

$name = "PowerShell.Scripts.EnableSpJsDebugging";

function Get-Modifications([Microsoft.SharePoint.Administration.SPWebApplication]$webApp){
    return $webApp.WebConfigModifications | ?{ $_.Owner -eq $name }
}

function Remove-Modifications([Microsoft.SharePoint.Administration.SPWebApplication]$webApp, 
    [Microsoft.SharePoint.Administration.SPWebService]$contentService){
    $mods = @();
    $removed = @()
    Get-Modifications($webApp) | %{ $mods = $mods + $_};
    $mods | %{ $removed += $webApp.WebConfigModifications.Remove($_) };
    $webApp.Update();
    $contentService.ApplyWebConfigModifications();
    $success = $removed | ?{ $_ -eq $true };
    $fails = $removed | ?{ $_ -eq $false };
    
    Write-Output "Removed $($success.Length) modifications.";
    if($fails.length > 0) {
        Write-Output "$($fails.Length) tries failed.";
    }
}

function Add-Modification([Microsoft.SharePoint.Administration.SPWebApplication]$webApp, 
    [Microsoft.SharePoint.Administration.SPWebService]$contentService){
    $mod = New-Object "Microsoft.SharePoint.Administration.SPWebConfigModification";
    $mod.Path = "configuration/system.web/compilation";
    $mod.Name = "debug";
    $mod.Sequence = 0;
    $mod.Owner = $name;
    $mod.Type = [Microsoft.SharePoint.Administration.SPWebConfigModification+SPWebConfigModificationType]::EnsureAttribute;
                     
    $mod.Value = "true";

    $unused = $webApp.WebConfigModifications.Add($mod);
    $webApp.Update();
    $contentService.ApplyWebConfigModifications();
    Write-Output "Added modification.";
}

$gc = Start-SPAssignment
try {
    $webApp = Get-SPWebApplication $WebApplication -AssignmentCollection $gc;
    $contentService = [Microsoft.SharePoint.Administration.SPWebService]::ContentService

    if($Remove){
        Remove-Modifications $webApp $contentService;
        Exit;
    }

    if((Get-Modifications $webApp).length -gt 0) {
        Write-Output "Modification already applied...";
        Exit;
    }

    Add-Modification $webApp $contentService;
} finally {
    Stop-SPAssignment $gc
}

Die Verwendung ist dann denkbar einfach:
> .\Enable-SpJsDebugging.ps1 -WebApplication http:/your.farm/site/
> .\Enable-SpJsDebugging.ps1 -WebApplication http:/your.farm/site/ -Remove

Sicherheitswarnung im Visual Studio beim debuggen des IIS

Wenn man im Visual Studio den Debugger an den IIS hängt kommt vorher ein Warnhinweis.

Das ist jetzt keine neue Information - und nach einer kurzen Suche kommt auch schnell wieder auf irgend einen Punkt an dem steht welcher Registry-Key auf welchen Wert gesetzt werden muss...

Da es mich aber immer etwas stört vorher suchen zu müssen und mich anschließend durch die Registry zu hangeln habe ich das mal als PowerShell-Skript abgelegt:

<#
 .SYNOPSIS
 Disable warning-messages when attaching VS to iis/w3wp.

 .DESCRIPTION
 When you attach VS to iis/w3wp for debugging, VS displays a warning.
 This script disables theese warnings by modifying the registry accordingly.
 WARNING: Make sure VS is not running when you start this script!

 .PARAMETER VsVersion
 The VS-version to modify - e.g. 10.0 for VS2010 or 14.0 for VS2015

 .PARAMETER AllVersions
 modify all installed Versions of VS

 .PARAMETER ReEnable
 Undo the changes done by this script - i.e. enable the warnings

 .PARAMETER IgnoreRunningVS
 continue script execution, even if a running VS is detected.

 .EXAMPLE
 .\Disable-VsDebuggerWarning.ps1 -VsVersion 12.0
 Disable the warning only for VS2013

#>

[CmdletBinding(DefaultParameterSetName="all")]
param (
 [Parameter(Mandatory=$false, ParameterSetName="all")]
    [switch]$AllVersions = $false,

 [Parameter(Mandatory=$true, ParameterSetName="one")]
    [string]$VsVersion,

 [Parameter(Mandatory=$false, ParameterSetName="all")]
    [Parameter(Mandatory=$false, ParameterSetName="one")]
    [switch]$ReEnable = $false,

 [Parameter(Mandatory=$false, ParameterSetName="all")]
    [Parameter(Mandatory=$false, ParameterSetName="one")]
    [switch]$IgnoreRunningVS = $false
)

$vsPath = "HKCU:\Software\Microsoft\VisualStudio\";

function Get-AllVsVersions()
{
    if(!(Test-Path $vsPath ))
    {
        Write-Error "No VisualStudio installations found. Exiting";
        Exit;
    }

    $versions = Get-ChildItem $vsPath | Where-Object { !$_.Name.EndsWith("_Config") } | Split-Path -Leaf

    Write-Host "$($versions.Length) VS installations found in registry: $versions"

    return $versions;
}

function Disable-Warning($version)
{
    $key = "DisableAttachSecurityWarning";
    $basePath = Join-Path $vsPath ($version + "\Debugger");
    $path = Join-Path $basePath $key;
    Write-Output "Setting $path to DWORD: $dword";

    # it's possible the key does not exist (e.g. in VS2015)
    $nonExistingItem = ((Get-ItemProperty $basePath -Name $key -ErrorAction SilentlyContinue) -eq $null);
    if($nonExistingItem)
    {
        if(!(Test-Path $basePath))
        {
            Write-Error "$basePath does not exist! Unable to proceed! Exiting";
            return;
        }

        New-ItemProperty $basePath -Name $key -Value $dword -PropertyType "DWord" 
    }
    else
    {
        Set-ItemProperty $basePath -Name $key -Value $dword
    }
}

$isVsRunning = ((Get-Process | where { $_.Name -eq "devenv" }).length -gt 0)

if($isVsRunning)
{
    Write-Host "A running VisualStudio was detected..." -ForegroundColor Red;
    if(!$IgnoreRunningVS)
    {
        Write-Output "If you want to prceed anyway, use the ""-IgnoreRunningVS""-Switch. Exiting.";
        Exit;
    }

    Write-Host "Ignoring the running VisualStudio... WARNING: this might not acually work! You have been warned!" -ForegroundColor Yellow;
}

$dword = 1;
if($ReEnable)
{
    $dword = 0;
}

$versions = @();
if($AllVersions)
{
    $versions = Get-AllVsVersions
}
elseif(![string]::IsNullOrWhiteSpace($VsVersion))
{
    $versions += $VsVersion
}
else
{
    Write-Host "Neither -VsVersion nor -AllVersions given. Nothing to do. :-(" -ForegroundColor Yellow
}

$versions | %{ Disable-Warning $_ }

Das Skript kann für eine bestimmte Version (z.B. mit "-VsVersion 10.0") oder einfach für alle Versionen ("-AllVersions") aufgerufen werden.

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);