 |
|
|
|
FR/EN |
|
| Edit in Browser | /_layouts/images/icxddoc.gif | /blog/sey/_layouts/formserver.aspx?XsnLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | FileType | xsn | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /blog/sey/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /blog/sey/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.2 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /blog/sey/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.3 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /blog/sey/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.4 | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /blog/sey/_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsx | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /blog/sey/_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsb | 255 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /blog/sey/_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsx | 256 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /blog/sey/_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsb | 256 |
|
|
| Edit in Browser | /_layouts/images/icxddoc.gif | /blog/sey/_layouts/formserver.aspx?XsnLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | FileType | xsn | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /blog/sey/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /blog/sey/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.2 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /blog/sey/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.3 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /blog/sey/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.4 | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /blog/sey/_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsx | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /blog/sey/_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsb | 255 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /blog/sey/_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsx | 256 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /blog/sey/_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsb | 256 |
|
|
|
 |
|
|
|
|
|
|
|
|
Retrouvez mon espace sur Developpez.com, l'espace Sharepoint sur developpez.com. Pour toute question relative à Sharepoint, vous pouvez tenter votre chance dans ce forum | |
|
| Reduce All |
Impersonation & SPSiteMapProvider |
Hello,
If you plan to use the SPSiteMapProvider within your code to browse the sites of your collection, you should know that by default, the returned sites are those that can be accessed by the current logged in user.
It's exactly the same behavior than the method GetSubwebsForCurrentUser(). Most of the case this behavior is wanted and complies to your needs but sometimes it may be necessary to browse all the sites even those that are not accessible to the current logged in user (or even anonymous).
In that case, your first think is of course to use SPSecurity.RunWithElevatedPrivileges...which is right but here is how to combine its usage with the SPSiteMapProvider object.
TreeView Tree = new TreeView(); SPSecurity.RunWithElevatedPrivileges(delegate() { using(SPSite Site = new SPSite("url of target site")) { using(SPWeb Web = Site.OpenWeb()) { SPControl.SetContextSite(this.Context, Site); SPControl.SetContextWeb(this.Context, Web); SPSiteMapProvider MapProvider = (SPSiteMapProvider)SiteMap.Providers["SPSiteMapProvider"]; SiteMapDataSource SiteData = new SiteMapDataSource(); SiteData.Provider = MapProvider; Tree.DataSource = SiteData; Tree.DataBind(); } } });
This code generates a treeview showing all the sites of the current site collection even those that cannot be seen by the logged in user. Of course this code also works with anonymous.
|
| Posted by Stéphane Eyskens at 10/5/2008 5:11:55 PM | Comments(0) |
|
SPSiteMapProvider et l'impersonation |
Salut,
Si vous souhaitez utiliser la classe buit-in SPSiteMapProvider dans vos composants pour parcourir la liste des sites d'une collection de sites, vous devez sans doute avoir constaté que la liste retournée correspond à la liste des sites auxquels l'utilisateur courant a accès.
C'est exactement le même comportement que la méthode GetSubwebsForCurrentUser(). Les 3/4 du temps, c'est le comportement souhaité donc cela ne pose pas de problème. Il peut par contre arriver que vous souhaitiez utiliser l'objet SPSiteMapProvider et néanmoins vouloir accéder à TOUS les sites de la collection sans tenir compte de ce que peut voir l'utilisateur connecté (ou non d'ailleurs si le mode anonyme est activé).
Dans une telle situation, votre première pensée va bien sûr vers SPSecurity.RunWithElevatedPrivileges...ce qui est effectivement une bonne idée :) mais encore faut-il savoir comment combiner l'usage de ce dernier avec le SPSiteMapProvider. Voici :
TreeView Tree = new TreeView(); SPSecurity.RunWithElevatedPrivileges(delegate() { using(SPSite Site = new SPSite("url du site cible")) { using(SPWeb Web = Site.OpenWeb()) { SPControl.SetContextSite(this.Context, Site); SPControl.SetContextWeb(this.Context, Web); SPSiteMapProvider MapProvider = (SPSiteMapProvider)SiteMap.Providers["SPSiteMapProvider"]; SiteMapDataSource SiteData = new SiteMapDataSource(); SiteData.Provider = MapProvider; Tree.DataSource = SiteData; Tree.DataBind(); } } });
Ce code génère un treeview affichant la liste de tous les sites de la collection, y compris ceux qui ne peuvent en théorie pas être accédés par l'utilisateur courant. Donc, cela fonctionne bien sûr aussi en mode anonyme. |
| Posted by Stéphane Eyskens at 10/5/2008 5:11:20 PM | Comments(0) |
|
Workflows avec Visual Studio ou Workflow avec SharePoint Designer |
Il n'est pas rare de croiser sur la toile des "débats" concernant la création de workflows pour SharePoint. Ces débats tournent souvent autour d'un même sujet : faut-il toujours créer les workflows avec Visual Studio sachant que SharePoint Designer offre la possibilité d'en générer plus facilement mais est considéré comme une bad practice.
Les amoureux des best practices affirmeront sans nul doute qu'il faut systématiquement utiliser Visual Studio.
Alors, voici ma vision des choses, peut-être pourra-t-elle contribuer à aider les béotiens dans ce difficile choix.
Tout d'abord, que reproche-t-on exactement à SharePoint Designer
- SharePoint Designer est TOUJOURS une bad practice :)). Si on se contente de cela, on peut passer son chemin et ne pas utiliser l'outil!
- SharePoint Designer ne permet pas d'implémenter des solutions de manière classique. En effet, il n'offre pas de solution de gestion de code source, ne permet pas de gérer plusieurs versions d'un même workflow, ne permet pas le (re)déploiement vers un autre environnement car il ne génère pas de solution (.wsp)
- SharePoint Designer est limité. En effet, tous les workflows générables OOTB sont limités au scope de site (web en termes SharePoint :)), ne permettent pas l'impersonation
- SharePoint Designer ne permet d'élaborer que des workflows simples
Voici à présent mes réponses à ces questions existentielles :).
- SD toujours une bad practice? En fait...oui, il ne s'agit à proprement parler de la meilleure manière qui soit d'implémenter des solutions dans SharePoint. Ceci dit, faut-il toujours l'éviter, ma réponse est clairement NON!
- SD ne suit pas un cycle de développement classique? En effet, mais est-ce toujours indispensable? Ne faut-il pas parfois mettre en balance la non possibilité (ou grande difficulté) de redéployer un workflow conçu avec SD avec la complexité et le temps qu'il faudrait pour concevoir un workflow équivalent via Visual Studio? Ne faut-il pas simplement considérer que le workflow demandé est parfois hyper spécifique et non réutilisable dans un autre contexte; ce qui dans ce cas, ne justifie pas absolument de recourir à Visual Studio? Qu'on se le dise, SharePoint Designer est un outil conçu pour attaquer directement la prod, donc en effet ce n'est pas du tout représentatif d'un cycle de développement classique! Pouvez-vous toujours vous permettre d'expliquer à un client qu'il est possible de développer ce workflow avec SD en 10 minutes mais qu'il vous faudrait plus ou moins 15 jours avec Visual Studio mais que bon, on a pas le choix parce que SD c'est une bad practice? Si oui, présentez-moi vos clients :).
- SD est limité? Oui mais vous pouvez tout à fait étendre sa liste d'actions et de conditions en développant vos propres activités WF et en les intégrant au concepteur de workflows de SD. Bien sûr, dans ce cas, vous devrez recourir à Visual Studio :)
- SD ne permet que de faire des workflows simples? FAUX et archi-faux. Essayez de développer un workflow avec Visual Studio qui génère plusieurs tâches spécifiques, met à jour des éléments de listes, envoie des e-mails incluant des données de l'élément ciblé et se mette en attente jusqu'à ce qu'une certaine condition soit remplie...Vous y arriverez bien sûr mais c'est déjà du sport et le risque d'induction de bug est accrû!
Donc, en gros, quelles sont les situations où il n'est pas aberrant de recourir à SharePoint Designer pour concevoir des workflows?
- Lorsque vous développez des workflows hyper spécifiques répondant à un cahier des charges particulier et dont l'usage ne dépassera jamais ce contexte spécifique
- Lorsqu'il s'agit d'un workflow très simple qui ne justifie pas nécessairement de passer par un cycle de développement entier (plate-forme de dev, test, acceptance et prod...), étapes indispensables lorsque vous déployez des workflows conçus avec Visual Studio. Ceci prend d'ailleurs toujours un certain temps pour ne pas dire un temps certain!
- Lorsqu'il s'agit d'un besoin évènementiel, court dans le temps. Exemple, vous devez créer un workflow sur une liste d'enquête qui ne servira que durant 10 jours...après, terminé, on ne s'en sert plus jamais! Dans ce cas, je ne vois pas pourquoi il faudrait sortir le bazouka pour tuer la mouche.
Passons maintenant à Visual Studio
C'est bien sûr l'outil idéal qui vous permet de développer des workflows simples ou complexes mais également vos propres activités WF. Activités que vous pouvez réutiliser au sein de différents workflows.
Par ailleurs, tout workflow développé avec Visual Studio est facilement encapsulable dans une fonctionnalité (feature) et une solution (.wsp ou .cab) SharePoint pouvant être déployée très facilement dans un autre environnement.
Enfin, ayant accès à l'API .NET et SharePoint, vous n'êtes bien sûr quasiment plus limité! Tous les scénaris liés aux processus métier peuvent désormais être envisagés et implémentés.
Après les avantages, les désavantages :))
Contrairement à SD, il faut un développeur voire un bon développeur pour créer un workflow avec VS.
Contrairement à SD, même pour un simple workflow que vous auriez fait en 5 min avec SD, il vous faudra consacrer plus de temps de développement, de documentation, de passage d'un environnement à l'autre et être plus vigilant car le risque d'induction de bug est clairement augmenté. Si bug, il y a, il faut encore repasser par tout le cycle (dev, test, acceptance, prod voire réplication prod avant la prod...)
Enfin, fini la simplicité! C'est là que j'entends les développeurs (dont je suis) me dire, "Ben oui, nous on fait pas de l'informatique pour faire du clic clic...:))" et en effet, c'est fini le clic clic, voici le sport. Sauf que là encore, Visual Studio (et surtout le 2008) peut faciliter la tâche du développeur en masquant toute la nécessité de créer des packages de solution (.wsp) afin de rendre le composant réutilisable, exportable dans d'autres environnements. Si le développeur manque de vigileance, il tombera alors exactement dans le même travers que celui auquel on fait face avec SD, à savoir un composant non packagé et donc non déployable correctement.
Moralité, quand est-il judicieux d'utiliser Visual Studio?
- Quand il est clairement établi que le workflow à développer peut-être utilisé à grande échelle et dans plusieurs sites/collections/applications/fermes de vos environnements SharePoint
- Quand une limite quelconque de SD apparaît et que vous estimez plus judicieux de développer le workflow avec VS plutôt que de créer une nouvelle activité pour SD
- Quand vous voulez être certain de pouvoir récupérer le workflow très facilement en cas de crash du système (corruption DB, ...). En effet, dans ce contexte, des solutions (.wsp) seront toujours un atout non négligeable car elles pourront être facilement redéployées sur un environnement tout neuf
- Quand vous disposez de suffisamment de ressources humaines et budgétaires
Voilà, j'espère que ceci pourra aider quelques personnes à faire le tri et à cerner les tenants et aboutissants de l'un ou l'autre choix. Vous l'aurez compris, je ne suis ni un extrêmiste de SD ni de VS. J'essaye simplement de choisir la solution la plus équilibrée en fonction de la demande.
Ciao :) |
| Posted by Stéphane Eyskens at 9/27/2008 2:47:38 PM | Comments(0) |
|
ContentTypeId in RenderPattern |
Hi,
Have you ever tried to deal with the contenttypeid in the renderpattern of a custom field?
If no, lucky you :))
If yes, you have probably noticed that this metadata cannot be used in statements such as IfEqual, IfSubstring, Switch etc...
The CAML doesn't throw any error but it's just not working! That said, you might wonder why it could be useful to use that field in the renderpattern of a custom field?
The idea is to simulate the behavior of the custom actions. When you create a custom action, you can specify its registration type as being "contenttype" and registrationid as being the content type ID of your content type. The impact of it is that your custom actions only show up for items whose the content type is the one mentioned in the registrationid
This is therefore very interesting.
Using ContentTypeId in the renderpattern of a custom field can have the same impact. For instance, if you do
<IfEqual>
<Expr1>
<Column Name="ContentTypeId" /> </Expr>
<Expr2>
your id
</Expr2>
<Then>
do something
</Then>
</IfEqual>
This would have the same impact as the registrationttype-contenttype for a custom action. The problem is that it's not working. For some reason, the condition is never satisfied even when you copy/paste the value of the content type id in Expr2.
You can also try with Switch, the result will be the same. So, it seems that it's not possible to simulate registrationtype-contenttype in the renderpattern.
It's not totally true, there is an alternative which involves javascript. It's possible to do it this way
<HTML><![CDATA[<div id="ColumnContent]]></HTML> <Column Name="ID" HTMLEncode="TRUE"/>
<ListProperty Select='Name'/> <HTML><![CDATA["></div>]]></HTML> <HTML><![CDATA[<script>var CurrentContentype = ']]></HTML> <Column Name="ContentTypeId" HTMLEncode="TRUE"/> <HTML><![CDATA[';if(CurrentContentype.toUpperCase().indexOf('your ct id in uppercase') != -1 )]]></HTML> <HTML><![CDATA[document.getElementById("ColumnContent]]></HTML> <Column Name="ID" HTMLEncode="TRUE"/>
<ListProperty Select='Name'/> <HTML><![CDATA[").innerHTML='some HTML here']]></HTML>
So, in other words, what it does is exactly the following
- Creating a div
- start a script by assigning the value of the current content type id to a variable called "CurrentContentType"
- Check whether this variable contains the ID of your content type (indexOf...). Don''t forget that you need to use indexOf since the contenttype ID of the list item won't be exactly the same as yours
- Then if indexOf is != -1, assigning some HTML to the div element
I'd have preferred to avoid involving script but this is the only way I found :). If you have any idea, don't hesitate to let me know
|
| Posted by Stéphane Eyskens at 7/24/2008 11:23:46 AM | Comments(0) |
|
Update of my SharePoint document rating system |
UPDATE : rel 4 now!
Now I think that the system will not change for a while. On top of what is described below, I've added a few new features
=> Number of votes are displayed when passing the mouse over the stars
=> The date of vote is included for each vote
=> A new report is available for site owners so that they can visualize all the votes for all the lists of a given site!
=> the list report is now available for all the list types
Hello,
I've just created a new release (rel 3) of my SharePoint document rating system in order to support rating on the following list types
- Documents
- Pictures
- Calendars
- Issue Lists
- Custom Lists
- Link Lists
The system does exactly what it did before but for all the templates mentioned above.
|
| Posted by Stéphane Eyskens at 7/13/2008 5:21:23 PM | Comments(0) |
|
Mise à jour de mon système d'évaluation |
UPDATE : rel 4 now!
Nouvelle mise à jour, ce coup-ci, le système restera comme ça un petit temps :). En plus de ce qui est décrit ci-dessous, voici les nouvelles fonctionnalités de la version 4
=> le nombre de votes s'affiche au survol de la souris sur les étoiles
=> la date de chaque vote est inclue dans le rapport
=> un nouveau rapport est disponible pour les propriétaires de sites afin qu'ils puissent consulter les votes de toutes les listes pour un site donné
=> le rapport de liste fonctionne avec tous les types de listes supportés.
J'ai créé une release 3 de mon système d'évaluation de documents pour l'étendre et donner la possibilité d'évaluer
- Des documents
- Des images
- Des évènements (calendrier)
- Des issues
- Des custom list
- Des liens
Le système fait exactement la même chose qu'avant, à savoir la possibilité d'évaluer et de commenter mais est étendu aux types de listes susnommées
|
| Posted by Stéphane Eyskens at 7/13/2008 5:19:23 PM | Comments(0) |
|
Custom Field + displaypattern + AJAX en CAML |
Salut,
J'ai récemment discuté avec une personne qui souhaitait afficher des données dynamiques provenant d'une DB externe à partir du displaypattern d'un custom field.
Comme vous le savez, le displaypattern est utilisé pour décrire comment une colonne doit s'afficher en mode liste.
Pour plus d'infos sur les patterns, vous pouvez consulter ce lien
Ces patterns ne sont pas tous simples à utiliser et le but de ce billet n'est pas de les expliquer mais simplement de faire un POC démontrant la possibilité d'utiliser de l'AJAX dans les patterns CAML.
Je l'ai juste fait pour le fun et n'ai pas encore eu à le réaliser dans un projet.
D'ailleurs si cela vous arrive, pensez à utiliser le BDC si possible...
Bon, voici néanmoins le scénario du POC
Disons que vous avez une liste SharePoint dans laquelle vous avez un custom field qui stocke l' ID d'un enregistrement de DB. Vous voulez offrir aux utilisateurs la possibilité de recevoir le libellé correspondant à cet ID de manière dynamique comme illustré ci-dessous
Comme vous pouvez le constater, J'ai les IDs qui viennent de la DB et je peux récupérer les libellés correspondants en cliquant sur l'image à côté des IDs.
Pour pouvoir obtenir ce résultat, vous devez faire ceci:
- Developper un custom field
- Developper le CAML du fichier FLDTYPES_xxx.xml associé à votre custom field et dans lequel vous devez inclure le code javascript
- Developper une page server ou utiliser un web service qui retourne une réponse XML aux requêtes AJAX.
Ceci fait, en cliquant sur une image, vous obtiendrez ceci:
Voici le code
Le custom field
C'est un simple POC qui utilise un SPFieldText hyper basique, juste cette classe suffit
using System; using System.Collections.Generic; using System.Text; using Microsoft.SharePoint;
namespace AjaxRawField { public class AjaxRawView : SPFieldText { public AjaxRawView(SPFieldCollection fields, string fieldName) : base(fields, fieldName) {
}
public AjaxRawView(Microsoft.SharePoint.SPFieldCollection fields, string typeName, string displayName) : base(fields, typeName, displayName) {
} } }
La page serveur
Rappelez-vous qu'il s'agit juste d'un POC, donc pour simplifier les choses, je n'ai pas été chercher les données d'une DB..Je ne fais que renvoyer du XML mais avec un peu d'imagination, celui-ci pourrait être généré après une query DB.
<%@ Page Language="C#" %> <%@ Import Namespace="System.Collections.Generic" %> <%@ Import Namespace="System.Threading" %>
<script runat="server"> protected override void Render(HtmlTextWriter writer) {
try { Thread.Sleep(1000); Response.ContentType = "text/xml"; int Id = Convert.ToInt16(Request.QueryString["Id"]); List<string> Ids = new List<string>(); for (int i = 0; i < 10; i++) { Ids.Add(string.Format("Label {0}",i.ToString())); } Response.Write(string.Format("<?xml version=\"1.0\" encoding=\"utf-8\"?><Root><Label Text=\"{0}\"/></Root>",Ids[Id])); } catch { Response.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><Root><Label Text=\"Not found\"/></Root>"); } } </script>
Si vous copiez/collez ce code dans une page aspx et la déployez dans le répertoire LAYOUTS, ça devrait suffire.
Le fichier CAML
C'est ici que ça se complique!
L'idée est d'écrire les fonctions AJAX dans le headerpattern du custom field et de les appeler dans le displaypattern
<?xml version="1.0" encoding="utf-8"?> <FieldTypes> <FieldType> <Field Name="TypeName">AjaxRawField</Field> <Field Name="ParentType">Text</Field> <Field Name="TypeDisplayName">AjaxRawField</Field> <Field Name="TypeShortDescription">AjaxRawField</Field> <Field Name="UserCreatable">TRUE</Field> <Field Name="ShowInListCreate">TRUE</Field> <Field Name="ShowInSurveyCreate">TRUE</Field> <Field Name="ShowInDocumentLibraryCreate">TRUE</Field> <Field Name="ShowInColumnTemplateCreate">TRUE</Field> <Field Name="FieldTypeClass">AjaxRawField.AjaxRawView, AjaxRawField,Version=1.0.0.0, Culture=neutral, PublicKeyToken=a17af6dda41cb365</Field> <Field Name="MaxLength">2000</Field> <RenderPattern Name="HeaderPattern"> <Switch> <Expr> <Property Select='Filterable'/> </Expr> <Case Value="FALSE"> </Case> <Default> <Switch> <Expr> <GetVar Name='Filter'/> </Expr> <Case Value='1'> <HTML><![CDATA[<SELECT ID="diidFilter]]></HTML> <Property Select='Name'/> <HTML><![CDATA[" TITLE=]]></HTML> <HTML>"$Resources:spscore,BusinessDataField_Filterby;</HTML> <Property Select='DisplayName' HTMLEncode='TRUE'/> <HTML><![CDATA[" OnChange='FilterField("]]></HTML> <GetVar Name="View"/> <HTML><![CDATA[",]]></HTML> <ScriptQuote> <Property Select='Name' URLEncode="TRUE"/> </ScriptQuote> <HTML> <![CDATA[,this.options[this.selectedIndex].value, this.selectedIndex, 0);' dir="]]> </HTML> <Property Select="Direction" HTMLEncode="TRUE"/> <HTML><![CDATA[">]]></HTML> <FieldFilterOptions BooleanTrue="$Resources:spscore,BusinessDataField_Yes;" BooleanFalse="$Resources:spscore,BusinessDataField_No;" NullString="$Resources:spscore,BusinessDataField_Empty;" AllItems="$Resources:spscore,BusinessDataField_All;"></FieldFilterOptions> <HTML><![CDATA[</SELECT><BR>]]></HTML> </Case> </Switch> </Default> </Switch> <Switch> <Expr> <Property Select='Sortable'/> </Expr> <Case Value="FALSE"> <Property Select='DisplayName' HTMLEncode="TRUE"/> </Case> <Default> <Switch> <Expr> <GetVar Name='SortDisable'/> </Expr> <Case Value='TRUE'> <Property Select='DisplayName' HTMLEncode="TRUE"/> </Case> <Default> <HTML><![CDATA[<A ID="diidSort]]></HTML> <Property Select='Name'/> <HTML> <![CDATA[" TITLE=]]> </HTML> <HTML>"$Resources:spscore,BusinessDataField_Sortby;</HTML> <Property Select='DisplayName' HTMLEncode='TRUE'/> <HTML><![CDATA[" SORTINGFIELDS="]]></HTML> <FieldSortParams/> <HTML><![CDATA[" HREF="javascript:" OnClick='javascript:SubmitFormPost("]]></HTML> <ScriptQuote NotAddingQuote="TRUE"> <PageUrl/> <HTML><![CDATA[?]]></HTML> <FieldSortParams/> </ScriptQuote> <HTML> <![CDATA[");javascript:return false;'>]]> </HTML> <Property Select='DisplayName' HTMLEncode="TRUE"/> <HTML><![CDATA[</A><IMG SRC="]]></HTML> <FieldSortImageURL/> <HTML><![CDATA[" ALT=]]></HTML> <Switch> <Expr> <GetVar Name='SortDir'/> </Expr> <Case Value='Asc'> <HTML>$Resources:spscore,BusinessDataField_Asc;</HTML> </Case> <Case Value='Desc'> <HTML>$Resources:spscore,BusinessDataField_Desc;</HTML> </Case> <Default> <HTML>""</HTML> </Default> </Switch> <HTML><![CDATA[ BORDER=0>]]></HTML> </Default> </Switch> </Default> </Switch> <HTML><![CDATA[<IMG SRC="]]></HTML> <FieldFilterImageURL/> <HTML><![CDATA[" BORDER=0 ALT=]]></HTML> <HTML>""</HTML> <HTML><![CDATA[>]]></HTML>
<HTML> <![CDATA[ <script language="javascript"> ReturnedIds = new Array(); HttpObject = null; function DoQuery(Id) { if(HttpObject == null) { if (window.XMLHttpRequest) { HttpObject = new XMLHttpRequest(); } else if (window.ActiveXObject) { HttpObject = new ActiveXObject("MSXML2.XMLHTTP.3.0"); } } HttpObject.DOMTarget = document.getElementById("__"+Id); if(HttpObject.DOMTarget && HttpObject.DOMTarget.innerHTML.indexOf("-")==-1) { HttpObject.DOMTarget.innerHTML = HttpObject.DOMTarget.innerHTML + "- waiting for server..."; HttpObject.open("GET", "/_layouts/GetTitles.aspx?id="+Id, true); HttpObject.setRequestHeader("Content-Type","text/xml; charset=utf-8"); HttpObject.setRequestHeader("Host", "localhost"); readyStateChangeHandler = AjaxAnswer; HttpObject.onreadystatechange = readyStateChangeHandler; HttpObject.send(); } } function AjaxAnswer() { if (HttpObject.readyState==4 && HttpObject.status==200) { var xml = HttpObject.responseXML; if (xml.documentElement) { var rows = xml.documentElement.getElementsByTagName("z:Label"); if (rows.length==0) { rows = xml.documentElement.getElementsByTagName("Label"); } if (rows.length > 0) { for (var i = 0; i < rows.length; i++) { if (rows[i].getAttributeNode("Text")!=null) { HttpObject.DOMTarget.innerHTML = parseInt(HttpObject.DOMTarget.innerHTML) + "-" +rows[i].getAttributeNode("Text").nodeValue; } } } } } } </script> ]]>
</HTML> </RenderPattern>
<RenderPattern Name="DisplayPattern"> <HTML><![CDATA[<table><tr><td id="__]]></HTML> <Column HTMLEncode="TRUE"/> <HTML><![CDATA[">]]></HTML> <Column HTMLEncode="TRUE"/> <HTML><![CDATA[</td><td><img onclick="DoQuery(']]></HTML> <Column HTMLEncode="TRUE"/> <HTML><![CDATA[')" src="_layouts/images/BDUPDATE.GIF"/></td></tr></table>]]></HTML> </RenderPattern> </FieldType>
</FieldTypes>
------------------------------------------------------------------
Voilà c'est fait! Simple non?
Déployez ce fichier XML dans le répertoire XML du 12 et vous aurez une nouvelle colonne vous permettant d'effectuer du rendering dynamique en mode liste.
|
| Posted by Stéphane Eyskens at 7/3/2008 11:35:26 PM | Comments(0) |
|
CustomField + Dynamic data (AJAX) in displaypattern |
Hello,
I've recently discussed with someone who wanted to render dynamic data coming from an external database from within the CAML displaypattern of a custom field.
As you know, the displaypattern is used to describe the display behavior of a list column when rendered in raw view.
To have more info on patterns, you can refer to MSDN at the following link
All those patterns are not so easy to use and my purpose here is not to explain them but just to make a kind of POC on using AJAX within the CAML patterns. I just did if for fun and I didn't have to use that in a project so far...:)
Also, remember that if possible, the preferred option for such requirements is to use the BDC
Well, this said, here is the scenario of the POC
Say, you've got a SharePoint list in which you store a custom field that holds the ID value of a Database record. You want to give users the possibility to retrieve the label associated to the ID dynamically from within a list view.
As you can see here, I've got the IDs coming from the database and I want to retrieve the corresponding label by clicking on the picture next to the ID.
To achieve this behavior, you need at list three things
- Develop a custom field (derived from whatever type)
- Develop the necessary CAML file FLDTYPES_xxx.xml associated to your custom fields in which you'll include the javascript code
- Develop a server page or use a web service or whatever that will return the XML answer to the AJAX query.
Once done, when clicking on a picture you'll get the following
So, here is the code
The custom field
I just make this POC using a SPFieldText, so just having this class is enough
using System; using System.Collections.Generic; using System.Text; using Microsoft.SharePoint;
namespace AjaxRawField { public class AjaxRawView : SPFieldText { public AjaxRawView(SPFieldCollection fields, string fieldName) : base(fields, fieldName) {
}
public AjaxRawView(Microsoft.SharePoint.SPFieldCollection fields, string typeName, string displayName) : base(fields, typeName, displayName) {
} } }
The server page
Here remember that it's just a POC, so the data doesn't come from a DB to keep things simple...I just return some XML but with a bit of imagination, this could be produced after a DB query :)
<%@ Page Language="C#" %> <%@ Import Namespace="System.Collections.Generic" %> <%@ Import Namespace="System.Threading" %>
<script runat="server"> protected override void Render(HtmlTextWriter writer) {
try { Thread.Sleep(1000); Response.ContentType = "text/xml"; int Id = Convert.ToInt16(Request.QueryString["Id"]); List<string> Ids = new List<string>(); for (int i = 0; i < 10; i++) { Ids.Add(string.Format("Label {0}",i.ToString())); } Response.Write(string.Format("<?xml version=\"1.0\" encoding=\"utf-8\"?><Root><Label Text=\"{0}\"/></Root>",Ids[Id])); } catch { Response.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><Root><Label Text=\"Not found\"/></Root>"); } } </script>
If you copy/paste the above code within a aspx page and you deploy it to the LAYOUTS folder, it's enough.
CAML File
And now, here is the big deal :), writing the CAML in the custom field definition file
The idea is to write the ajax functions in the headerpattern of the custom field and to call some of these in the displaypattern when rendering list items
<?xml version="1.0" encoding="utf-8"?> <FieldTypes> <FieldType> <Field Name="TypeName">AjaxRawField</Field> <Field Name="ParentType">Text</Field> <Field Name="TypeDisplayName">AjaxRawField</Field> <Field Name="TypeShortDescription">AjaxRawField</Field> <Field Name="UserCreatable">TRUE</Field> <Field Name="ShowInListCreate">TRUE</Field> <Field Name="ShowInSurveyCreate">TRUE</Field> <Field Name="ShowInDocumentLibraryCreate">TRUE</Field> <Field Name="ShowInColumnTemplateCreate">TRUE</Field> <Field Name="FieldTypeClass">AjaxRawField.AjaxRawView, AjaxRawField,Version=1.0.0.0, Culture=neutral, PublicKeyToken=a17af6dda41cb365</Field> <Field Name="MaxLength">2000</Field> <RenderPattern Name="HeaderPattern"> <Switch> <Expr> <Property Select='Filterable'/> </Expr> <Case Value="FALSE"> </Case> <Default> <Switch> <Expr> <GetVar Name='Filter'/> </Expr> <Case Value='1'> <HTML><![CDATA[<SELECT ID="diidFilter]]></HTML> <Property Select='Name'/> <HTML><![CDATA[" TITLE=]]></HTML> <HTML>"$Resources:spscore,BusinessDataField_Filterby;</HTML> <Property Select='DisplayName' HTMLEncode='TRUE'/> <HTML><![CDATA[" OnChange='FilterField("]]></HTML> <GetVar Name="View"/> <HTML><![CDATA[",]]></HTML> <ScriptQuote> <Property Select='Name' URLEncode="TRUE"/> </ScriptQuote> <HTML> <![CDATA[,this.options[this.selectedIndex].value, this.selectedIndex, 0);' dir="]]> </HTML> <Property Select="Direction" HTMLEncode="TRUE"/> <HTML><![CDATA[">]]></HTML> <FieldFilterOptions BooleanTrue="$Resources:spscore,BusinessDataField_Yes;" BooleanFalse="$Resources:spscore,BusinessDataField_No;" NullString="$Resources:spscore,BusinessDataField_Empty;" AllItems="$Resources:spscore,BusinessDataField_All;"></FieldFilterOptions> <HTML><![CDATA[</SELECT><BR>]]></HTML> </Case> </Switch> </Default> </Switch> <Switch> <Expr> <Property Select='Sortable'/> </Expr> <Case Value="FALSE"> <Property Select='DisplayName' HTMLEncode="TRUE"/> </Case> <Default> <Switch> <Expr> <GetVar Name='SortDisable'/> </Expr> <Case Value='TRUE'> <Property Select='DisplayName' HTMLEncode="TRUE"/> </Case> <Default> <HTML><![CDATA[<A ID="diidSort]]></HTML> <Property Select='Name'/> <HTML> <![CDATA[" TITLE=]]> </HTML> <HTML>"$Resources:spscore,BusinessDataField_Sortby;</HTML> <Property Select='DisplayName' HTMLEncode='TRUE'/> <HTML><![CDATA[" SORTINGFIELDS="]]></HTML> <FieldSortParams/> <HTML><![CDATA[" HREF="javascript:" OnClick='javascript:SubmitFormPost("]]></HTML> <ScriptQuote NotAddingQuote="TRUE"> <PageUrl/> <HTML><![CDATA[?]]></HTML> <FieldSortParams/> </ScriptQuote> <HTML> <![CDATA[");javascript:return false;'>]]> </HTML> <Property Select='DisplayName' HTMLEncode="TRUE"/> <HTML><![CDATA[</A><IMG SRC="]]></HTML> <FieldSortImageURL/> <HTML><![CDATA[" ALT=]]></HTML> <Switch> <Expr> <GetVar Name='SortDir'/> </Expr> <Case Value='Asc'> <HTML>$Resources:spscore,BusinessDataField_Asc;</HTML> </Case> <Case Value='Desc'> <HTML>$Resources:spscore,BusinessDataField_Desc;</HTML> </Case> <Default> <HTML>""</HTML> </Default> </Switch> <HTML><![CDATA[ BORDER=0>]]></HTML> </Default> </Switch> </Default> </Switch> <HTML><![CDATA[<IMG SRC="]]></HTML> <FieldFilterImageURL/> <HTML><![CDATA[" BORDER=0 ALT=]]></HTML> <HTML>""</HTML> <HTML><![CDATA[>]]></HTML>
<HTML> <![CDATA[ <script language="javascript"> ReturnedIds = new Array(); HttpObject = null; function DoQuery(Id) { if(HttpObject == null) { if (window.XMLHttpRequest) { HttpObject = new XMLHttpRequest(); } else if (window.ActiveXObject) { HttpObject = new ActiveXObject("MSXML2.XMLHTTP.3.0"); } } HttpObject.DOMTarget = document.getElementById("__"+Id); if(HttpObject.DOMTarget && HttpObject.DOMTarget.innerHTML.indexOf("-")==-1) { HttpObject.DOMTarget.innerHTML = HttpObject.DOMTarget.innerHTML + "- waiting for server..."; HttpObject.open("GET", "/_layouts/GetTitles.aspx?id="+Id, true); HttpObject.setRequestHeader("Content-Type","text/xml; charset=utf-8"); HttpObject.setRequestHeader("Host", "localhost"); readyStateChangeHandler = AjaxAnswer; HttpObject.onreadystatechange = readyStateChangeHandler; HttpObject.send(); } } function AjaxAnswer() { if (HttpObject.readyState==4 && HttpObject.status==200) { var xml = HttpObject.responseXML; if (xml.documentElement) { var rows = xml.documentElement.getElementsByTagName("z:Label"); if (rows.length==0) { rows = xml.documentElement.getElementsByTagName("Label"); } if (rows.length > 0) { for (var i = 0; i < rows.length; i++) { if (rows[i].getAttributeNode("Text")!=null) { HttpObject.DOMTarget.innerHTML = parseInt(HttpObject.DOMTarget.innerHTML) + "-" +rows[i].getAttributeNode("Text").nodeValue; } } } } } } </script> ]]>
</HTML> </RenderPattern>
<RenderPattern Name="DisplayPattern"> <HTML><![CDATA[<table><tr><td id="__]]></HTML> <Column HTMLEncode="TRUE"/> <HTML><![CDATA[">]]></HTML> <Column HTMLEncode="TRUE"/> <HTML><![CDATA[</td><td><img onclick="DoQuery(']]></HTML> <Column HTMLEncode="TRUE"/> <HTML><![CDATA[')" src="_layouts/images/BDUPDATE.GIF"/></td></tr></table>]]></HTML> </RenderPattern> </FieldType>
</FieldTypes>
------------------------------------------------------------------
You're done! Easy no?
Just deploying that XML file in the XML folder of the 12 hive will show you a new column type with AJAX call enabled in row view.
|
| Posted by Stéphane Eyskens at 7/3/2008 11:11:46 PM | Comments(0) |
|
Longue Opération |
Salut
Lorsque vous exécutez des opérations longues ou susceptibles de l'être dans SharePoint, pour faire patienter vos visiteurs, vous pouvez utiliser la classe SPLongOperation qui a été spécialement conçue pour cela.
Vous pouvez l'utiliser comme suit:
SPLongOperation LongOp = new SPLongOperation();
LongOp.LeadingHTML = "My long operaation..."
LongOp.Begin();
//Vos opérations longues :)
LongOp.End(ReturnUrl);
L'avantage est donc que pendant que votre code s'exécute, le visiteur voit le message spécifié dans LeadingHTML
|
| Posted by Stéphane Eyskens at 6/26/2008 10:21:42 PM | Comments(1) |
|
>> |
|
|
|
|
|
|
|
|