Zum Hauptinhalt wechseln

Michael Klingner

Startet die Suche
Homepage
  

Michael Klingner
Andere Blogs
Diese Liste enthält keine Elemente.
MCPD
MCTS
SOD und ihrer Eigenheiten
Die in SharePoint 2010 neue Möglichkeit, JavaScript Dateien bei Bedarf zu laden, ermöglicht eine bessere Kontrolle der zu einzelnen Dateien und deren Abhängigkeiten untereinander. In Zeiten in denen immer mehr von JavaScript, und damit vom Browser, abgearbeitet wird ist eine solche Funktionalität essentiell. Das "Script on Demand" (SOD) besteht im Grunde das 5 Funktionen:

NotifyScriptLoadedAndExecuteWaitingJobs: Ein Trigger, der SOD mitteilt das ein bestimmtes JavaScript geladen wurde.
ExecuteOrDelayUntilScriptLoaded: Ein Callback der getriggter wird wenn das angegebene Script geladen wurde.
RegisterSod: Zum Registrieren der JavaScript Datei anhand von Schlüssen und Pfad zur Datei.
RegisterSodDep: Zum Definieren von Abhängigkeiten zu anderen Skripten.
EnsureScriptFunc: Der Auslöser, der die gewünschte JavaScript Datei inkl. ihrer Abhängigkeiten lädt.

Hier anhand eine kurzen Beispiels zum Laden von jQuery und der jQuery UI:

Letzte Zeile im Script für jQuery UI:
...
if (typeof (NotifyScriptLoadedAndExecuteWaitingJobs) == "function") { 
  NotifyScriptLoadedAndExecuteWaitingJobs("jquery-ui.1.8.16.js"); 
}; 

SOD Register Block im HTML
ExecuteOrDelayUntilScriptLoaded(function(){
  alert('jQuery UI is ready!');
},'jquery-ui.1.8.16.js');
RegisterSod('jquery-ui.1.8.16.js','/ScriptResource.axd?d=RU7uNr...');
RegisterSod('jquery.1.6.4.js','/ScriptResource.axd?d=N64HMcQl...');
RegisterSodDep('jquery-ui.1.8.16.js','jquery.1.6.4.js');
EnsureScriptFunc('jquery-ui.1.8.16.js');

Wie alles im Leben hat auch SOD seine Eigenheiten und Macken. Diese möchte ich nun kurz beschreiben und die möglichen Workarounds beschreiben:

RegisterSodDep({Schlüssel des Kind JavaScript},{Schlüssel des Eltern JavaScript}

Bei der Registrierung eines Skripts via RegisterSod wird der Schlüssel intern als lowercase Zeichenfolge gespeichert. Bei der Definition der Anhängigkeit vom Kind zum Elter wird aber der Schlüssel vom Elter nicht lowercase gespeichert. Wenn nun uppercase Zeichen im Schlüssel vorhanden sind, kann SOD das Elternelement nicht finden und überspringt diese Abhängigkeit.
Workaround:
Bei Schlüsseln in SOD am besten immer alles lowercase angeben oder bei RegisterSodDep mindestens den 2. Parameter lowercase angeben.

Laden von JavaScript Dateien aus geschützten Ordnern

Wenn eine JavaScript Datei z.B. in der Style Library liegt und das Portal eine Authentifizierung benötigt, kann es sein das SOD an seine Grenzen stößt und nicht mitbekommt das bestimmte Dateien geladen wurden. SOD ist eventbasierend und feuert ein OnLoad Event bei erfolgreichem Laden einer JavaScript Datei. Bei z.B. NTLM ist es nun leider so das die ersten Anfragen mit einer 401 quittiert werden. Diesen Ablauf kann man sehr schön im Firebug beobachten. Durch die geworfenen Fehler, ist das Skript für SOD nun nicht geladen worden und befindet sich im "Pending" Status.
Workaround:
Damit SOD auch Dateien aus geschützten Ordnern lesen kann, muss etwas in die Trickkiste gegriffen werden. In der JavaScript Datei muss in der internen Variable von SOD _v_dictSod der Status per Hand gesetzt werden. Sollte sich nun das jQuery Script in einem solchen Ordner befinden muss die letzte Zeile wie folgt lautet:
_v_dictSod['jquery.1.6.4.js'].state = 4;
Das ist zwar etwas durch die Brust-in-Auge aber es funktioniert.
JS Bookmarks für SharePoint

Meine Mathematikdozent hat mir seiner Zeit mal erklärt das Mathematiker von Natur aus faul sind. Darum entwickeln sich auch so kryptische Abkürzungen für komplizierte Sachverhalt, wie z.B. das Summenzeichen. Der gleiche Sachverhält lässt sich auch für Entwickler sagen. Warum sonst hätten man z.B. die Lambda Ausdrücke oder den Ternary-Operator definiert. Zu diesen faulen Menschen möchte ich mich auch zählen. Aus meiner Faulheit entstanden nun aber keine neuen mathematischen Symbole oder Ausdrücke. Bei mir ist die Faulheit darin ausgeprägt, meine Parameter in die Adresszeile einzutippen. Für diese "Arbeit" habe ich mir ein paar Favoriten mit JS Code erstellt die mir folgendes abnehmen:

  • Settings.aspx: Bringt mich zu den _layouts/settings.aspx des aktuellen Webs
  • EditMode: Zeigt mit die aktuelle Seite im EditMode (klappt leider nur wenn ich sie vorher ausgescheckt habe)
  • AllItems.aspx: Bringt mich zur AllItems.aspx der aktuellen Seitenbibliothek
  • Explorer: Öffnet die aktuelle Bibliothek im Explorer


Die Sammlung meiner Favoriten können hier heruntergeladen werden. Einfach in den Favoritenordner des IE (%USERPROFILE%\Favorites\Links) entpacken.

Viel Spaß beim Klicken.

 

Solution für der „nach“-Bug in den Ankündigungen
Auf Basis des Blogeintrags meines Kollegen Fabian Moritz, habe ich mich an die Umsetzung des Workarounds gemacht. Der Aufwand bei dieser Solution ist nicht der Workaround an sich, sondern das "Wie bekomme ich jQuery in den SharePoint?". Ich wählte den Weg über das DelegateControl "AdditionalPageHead". Dieser kann ja mit Hilfe von Features mit beliebigen Controls gefüllt werden.
 
Nach der Installation und Aktivierung des Feature wird nun auf jeder Seite das 'nach' durch ein 'von' ersetzt.

Die Solution kann hier heruntergeladen werden.

Installationsanleitung via PS:
$path = ((Get-Location -PSProvider FileSystem).ProviderPath)+"/ITaCS.Straighten.wsp"
Add-SPSolution -LiteralPath $path
Install-SPSolution -Identity "ITaCS.Straighten.wsp" -GACDeployment -WebApplication {Url der WebApplication}
Enable-SPFeature -Identity ITaCS.Straighten_ByJavaScript -Url {Url der SiteCollection}
HOWTO: ein SPWeb readonly schalten
Es gibt ja schöne Funktionen in stsadm um eine SiteCollection (SPSite) auf readonly zu setzen, aber was ist, wenn man nur eine bestimmte Site (SPWeb) eine Schreibschutz verpassen möchte oder muss. Diese Funktion findet sich leider nicht in stsadm, kann aber über die Berechtigungsstufen erreicht werden. Dazu müssen diese nur angepasst werden und alle schreibenden Rechte entzogen werden.

Achtung! Damit nur ab dem gewählte SPWeb gesperrt wird, muss die Vererbung der Berechtigungsstufen aufgebrochen werden. Wenn die Vererbung danach wieder aktiviert wird, werden auch alle aufgebrochenen Vererbungen in Listen und Elementen auch wieder aktiviert!

Hier meine Lösung:
/// 
/// Akiviert den Schreibschutz durch das entfernen aller Schreibrechte aus den Berechtigungsstufen
/// 
public static void LockWeb(SPWeb web)
{
	//Kontrolle ob das Web schon gesperrt wurde
	if (String.IsNullOrEmpty(web.Properties["_lockweb_OriginalPermissions"]))
	{
		//die Original Definition für das Entsperren speichern
		web.Properties["_lockweb_OriginalPermissions"] = web.RoleDefinitions.Xml;
		web.Properties.Update();
		//damit nur das aktuelle Web gesperrt wird, muss die Vererbung aufgehoben werden
		if (!web.HasUniqueRoleDefinitions)
		{
			web.RoleDefinitions.BreakInheritance(true, true);
		}
		var roles = web.RoleDefinitions;
		//entferne nun alle Schreibrechte aus den einzelnen Stufen
		foreach (SPRoleDefinition role in roles)
		{
			role.BasePermissions &= SPBasePermissions.BrowseDirectories | SPBasePermissions.BrowseUserInfo | SPBasePermissions.Open | SPBasePermissions.OpenItems | SPBasePermissions.UseClientIntegration | SPBasePermissions.UseRemoteAPIs | SPBasePermissions.ViewFormPages | SPBasePermissions.ViewListItems | SPBasePermissions.ViewPages | SPBasePermissions.ViewUsageData | SPBasePermissions.ViewVersions;
			role.Update();
		}
	}
}
/// 
/// Deakiviert den Schreibschutz durch das reaktivieren der original Berechtigungsstufen
/// 
public static void UnlockWeb(SPWeb web, Action onErrorHandler)
{
	//Kontrolle ob das web gesperrt wurde
	if (web.HasUniqueRoleDefinitions)
	{
		//lädt die Original Rechte aus dem PropertyBag
		string roleDefXml = web.Properties["_lockweb_OriginalPermissions"];
		if (!String.IsNullOrEmpty(roleDefXml))
		{
			XmlDocument xDoc = new XmlDocument();
			xDoc.LoadXml(roleDefXml);
			//verteilt die Rechte aus der XML an die Stufen
			foreach (XmlNode roleNode in xDoc.SelectNodes("/Roles/Role"))
			{
				try
				{
					int id = Convert.ToInt32(roleNode.Attributes["ID"].Value);
					SPBasePermissions basePermissions = (SPBasePermissions)Enum.Parse(typeof(SPBasePermissions), roleNode.Attributes["BasePermissions"].Value);
					var roleDef = web.RoleDefinitions.GetById(id);
					roleDef.BasePermissions = basePermissions;
					roleDef.Update();
				}
				catch (Exception ex)
				{
					if (onErrorHandler != null) onErrorHandler(ex);
				}
			}
			//entfernt das Backup der Original Rechte
			web.Properties["_lockweb_OriginalPermissions"] = null;
			web.Properties.Update();
		}
	}
}
Filterung eines Taxonomy Feldes in einem XsltListViewWebPart über eine WebPart Connection mit mehreren Werten

Ich stand kürzlich vor der Aufgabe ein Feld in einem XsltListViewWebPart über eine WebPart-Verbindung zu filtern. So weit nicht weiter schwer, hat sich doch mein Langzeitgedächtnis gemeldet und etwas von ITransformableFilterValues und ConnectionProvider geplaudert. Dass das Ziel-Feld ein Taxonomy - aka. Metadaten, aka. Laufzeitspeicherverwaltung (ach ja, gute alte Beta-Zeit) - Feld ist sollte da doch nicht weiter stören, dachte ich. Das sind doch hierarchisch aufgebaute Daten, da reicht das doch bestimmt nur den Hauptknoten anzugeben… So weit, so falsch gedacht.
In ITransformableFilterValues reicht es eigentlich eine Liste von Werten zu übergeben nach denen gefiltert werden soll. Dumm nur das XsltListViewWebPart immer nur den ersten Wert nutzt und dann auch nur mit dem Label des Taxonomy Wertes vergleicht. Man kann also OOTB nur nach einem Anzeigewert suchen und dann wird auch die Hierarchie nicht beachtet, mit dem Resultat: man erhält nur die Zeilen mit genau dem Wert im Taxonomy Feld.
Es muss doch funktionieren, dachte mein Forscherhirn, wenn ich die Layouts im XsltListViewWebPart benutze kann ich doch auch mehrerer Werte auswählen und ich der beachtet die Hierarchie sogar.


Ein Blick in die von der FlyOut-Filterung produzierten Url lässt hoffen:


 
…&FilterField1=BlogTopics­&­FilterValue1=1%2C2&­FilterOp1=In&­FilterLookupId1=1­&FilterData1=­1%2Cf7ed193d-aa57-4df5-a33d-96bc75e4551d
Interessant hierbei sind FilterValue1, FilterOp1 & FilterLookupId1 (Der Filter funktioniert auch ohne FilterData1, nur der FlyOut benötigt diesen). Das heißt also der WebPart filtert nicht über den Anzeigewert, sondern kann auch über die IDs filtern…Moment! IDs? Welche IDs? Das sind die IDs aus der TaxonomyHiddenList. Das Taxonomy Feld ist ja nix weiter als ein mächtigeres Nachschlagefeld und an die IDs kommt man einfach. Nach einem Blick in diese Liste offenbart sich die Tücke(n) im Detail: 1 & 2 sind die verwandt! 1 ist das Eltern Element von 2. Und in dieser Liste werden nur Elemente aus dem Taxonomy-Store aufgelistet die auch genutzt werden. Aber erst mal bei Seite (mhhh… eigentlich ein gutes Thema für einen 2. Eintrag).
Das Problem ist erst mal: Wie bekomme ich den XsltListViewWebPart dazu nach meinen IDs zu filtern? Ein Blick in das Objektmodell vom XsltListViewWebPart offenbart ein interessantes Property: FilterOperations. Hier können alle Filterungen (u.a. auch die der URL Parameter Filterung) gefunden werden. Nun ist das Ziel bekannt! FilterOperations müssen angepasst werden! Hier meine Lösung:

// dieser WebPart ist nur ein Dummy und soll die Grundfunktionalität abbilden 
public  class  TaxonomyFilterWebPart  : System.Web.UI.WebControls.WebParts.WebPart , ITaxonomyFilterValueProvider 
{
	private  TaxonomyFilter  _filter;
	public  TaxonomyFilterWebPart()
	{
		_filter = new  TaxonomyFilter (this );
	}
	public  new  WebPartManager  WebPartManager { get  { return  base .WebPartManager; } }
	///  
	/// gibt an das der WebPart Filterwerte hat 
	///  
	public  bool  HasValue { get  { return  true ; } }
	///  
	/// exemplarische Filterwerte 
	///  
	public  string  FilterValue { get  { return  "1,2,3" ; } }
	///  
	/// übergibt den TaxonomyFilterüür die Verbindung zum XsltListViewWebPart 
	///  
	[ConnectionProvider ("Taxonomy Filter" , "TaxonomyFilterConnectionTrans" , AllowsMultipleConnections = true )]
	public  ITransformableFilterValues  SetFilterConnection() { return  _filter; }
}
///  
/// Alle notwendigen Eigenschaften des Providers für den TaxonomyFilter 
///  
public  interface  ITaxonomyFilterValueProvider 
{
	WebPartManager  WebPartManager { get ; }
	String  ClientID { get ; }
	bool  HasValue { get ; }
	string  FilterValue { get ; }
	event  EventHandler  Load;
}
public  class  TaxonomyFilter  : ITransformableFilterValues 
{
	///  
	/// Platzhalter für die spätere einfache Wiedererkennung des FilterOperators 
	///  
	private  string  PlaceHolder { get  { return  "@TaxonomyFilter@"  + _provider.ClientID; } }
	private  ITaxonomyFilterValueProvider  _provider;
	public  TaxonomyFilter(ITaxonomyFilterValueProvider  provider)
	{
		_provider = provider;
		_provider.Load += Provider_Load;
	}
	///  
	/// Beim Load wird nach der Verbindung zwischen XsltListViewWebPart & Provider gesucht, 
	/// um das spätere Databinding Event zu registrieren. 
	///  
	void  Provider_Load(object  sender, EventArgs  e)
	{
		foreach  (WebPartConnection  wpc in  _provider.WebPartManager.Connections)
		{
			if  (wpc.Provider == _provider && wpc.Consumer is  XsltListViewWebPart )
			{
				wpc.Consumer.DataBinding += new  EventHandler (XsltWP_DataBinding);
				break ;
			}
		}
	}
	///  
	/// Sucht nach dem Platzhalter in den FilterOperations vom XsltListViewWebPart 
	/// und ersetzt ihn mit den richtigen Werten und dem gewünschten OperationType. 
	///  
	void  XsltWP_DataBinding(object  sender, EventArgs  e)
	{
		//wenn der Provider keine Werte zum Filter hat, dann passiert auch nix 
		if  (!_provider.HasValue)
			return ;
		XsltListViewWebPart  xwp = sender as  XsltListViewWebPart ;
		String  filterValue = PlaceHolder;
		foreach  (FilterOperation  filterOp in  xwp.FilterOperations.Values)
		{
			//Suche nach dem Platzhalter 
			if  (filterOp.FilterValue == filterValue)
			{
				//Setzen des richtigen Filterwertes 
				String  realFilteValue = _provider.FilterValue;
				filterOp.FilterValue = realFilteValue;
				//wenn der Filterwert leer ist, kann er keine Lookupid sein 
				if  (!String .IsNullOrEmpty(realFilteValue))
				{
					filterOp.IsFieldLookupId = true ;
					filterOp.OperationType = "In" ;
				}
				break ;
			}
		}
	}
	public  bool  AllowAllValue { get  { return  true ; } }
	public  bool  AllowEmptyValue { get  { return  true ; } }
	public  bool  AllowMultipleValues { get  { return  true ; } }
	public  string  ParameterName { get  { return  "Taxonomy Filter" ; } }
	///  
 	/// Fragt den Provider ab ob Filterwerte vorhanden sind und setzt dann ggf. den Platzhalter ein 
 	///  
 	public  System.Collections.ObjectModel.ReadOnlyCollection  ParameterValues
 	{
 		get 
 		{
 			List  list = new  List ();
 			if  (_provider.HasValue)
 				list.Add(PlaceHolder);
 			return  new  System.Collections.ObjectModel.ReadOnlyCollection (list);
 		}
 	}
 }

 ‭(Ausgeblendet)‬ Administratorhyperlinks