Monday, December 12, 2011

Sharepoint 2010 - Email Event Receiver Issues

The Email event receiver is not so simple to create and execute...
I experienced some issues for the correct configuration.

EmailReceived Method: wrong reference in Handle Email Receiver method created by VS
When you create the email event receiver you see the method EmailReceived. In Visual Studio, see properties of event receiver and you see "handle email receiver" = false.
If you set to true, VS creates method EmailReceiver with Microsoft.Sharepoint.SPEmailMessage insthead of Microsoft.Sharepoint.Utilities.SPEmailMessage.
This cause error in build event receiver.
I think is an issue/bug of Sharepoint 2010, but I did not found any documentation in MS.
Solution: The event receiver work fine without set "handle" property to true. Leave this setting to false.

Email Event Receiver: SPContext.Current is null
Solution: use list.ParentWeb, from the list property of method EmailReceived.

Email Event Receiver: if you have to process attachment of email
If your Email ER processes attachments and in Incoming Email Settings you configured for saving only attachments, but not email message, the ER fails to load.
Solution: go to Incoming Email Setting and set "Save original Message email"

Incoming Email Settings: you don't see all properties (Email Attachments, Email Message, Email Security, ecc)
If you attach Email Event Receiver, you will see only setting for email address of incoming email for Document Library.
Solution: Deactivate custom feature for Email ER, set your Incoming Email Settings, then activate feature.
Resourcehttp://blogs.msdn.com/b/malag/archive/2009/05/13/attachments-disappear-with-custom-email-event-handler.aspx


Deploy Event Receiver and ER don't fires
I created, built and  deployed my email event receiver. Library is correctly configured to receive email with attachment. But when I send mail, there's no mail or data in document library.
Solution: when you deploy and IIS resets, restart Sharepoint Timer Service (from services.msc). Then send mail and all works fine.
Resourcehttp://blogs.pointbridge.com/Blogs/mierop_steve/Pages/Post.aspx?_ID=2


Have you found other issues and have you resolved them?
Write me!

Friday, November 18, 2011

Sharepoint 2010 - Impersonate issue in Office and SP2010 when editing Document?


There's a strange behaviour in SharePoint 2010 with Office and impoersonate users.

The Scenario:

  • User A is logged into Windows.
  • User A opens SharePoint 2010 site and then signs in under a different user (User B).
  • The user (now logged in as User B in SharePoint) edits a Word document which is required to be checked out.
  • User B checks out document in SharePoint 2010
  • But when Word opens, a message is displayed saying 'This document is checked out by User B'.
or
  • User B opens Word document, client Word opens and ask for check-out.
  • User checks out document in Word
  • But when user try to save, an error message is displayed and in Sharepoint user see that document is checked out by User A.

The Word document should be able to be edited because the user context from SharePoint sent to Word should be as User B not User A.
It seems that Word is still opening up as User A and because the document is actually checked out to User B it can't be edited.

The answer is that Office and SharePoint do not share the same context in IE and do not share the same credentials.
All office apps open a new instance of Internet Explorer internally and then connect to the SharePoint site using that.
If the site is such that it can pass windows/domain credentials (i.e. it is listed as in the Intranet Zone) then Office will pass the default windows credentials instead.

As to working around it, there are a few possibilities:

  • Remove the site from the local "Intranet Sites" or "Trusted Sites" zone which will force authentication both for IE and for Office
  • Give UserA proper permissions and simply use that ID instead.
  • Remove permissions to the item from UserA which will force an authentication request
  • Log into the workstation as UserB

Regardless of the above, it is also not a best practice to have users using multiple logins when interacting with a site as it makes basic accountability almost impossible.
For example, if User B deletes a document but the credentials for User B are shared around the office,
you have no idea who actually made the change.


Tuesday, November 15, 2011

Google Voice in lingua italiana prepara la strada per l'Italia?

Questa sera è arrivato l'aggiornamento dell'applicazione Google voice ..
L'ho sempre tenuta qui sul mio nexus nella speranza ogni tanto di vedere l'ok al momento della verifica del numero.
Niente.. beh in Italia non è ancora attivo il servizio ok.

Però stasera arriva l'update e magia.. l'applicazione è tradotta in italiano!

Vano ancora il mio tentativo di configurazione : ancora non è possibile verificare il numero.

Che sia un timido segnale.che stia per arrivare anche qui questo servizio? Speriamo!!

E aspettiamo!

Monday, October 31, 2011

Sharepoint 2010 - Migrate List-based Workflows between Sites and Site Collections


Thank you Gavinmckay, you saved me!
From his blog: http://gavinmckay.wordpress.com/2011/08/29/howto-move-or-migrate-sharepoint-2010-list-based-workflows-between-sites-and-site-collections/

I’ve experienced this issue a lot when trying to migrate workflows between test SharePoint 2010 farms and production farms, in particular with workflows attached to lists. When moving a workflow to another site collection or server farm, the association to the list is broken and the workflow cannot be attached to the list. You also cannot use SharePoint designer to fix this via the standard methods as the unattached workflow cannot be reattached to the list.

List-based workflows are tied to three different lists – the “main” source list where the data is held (such as a Forms library or custom list), a task list, and a workflow history list. The latter in particular is tricky, because it is a hidden list and cannot be viewed via the normal interface.

The fix for this is to modify the source workflow files to force the workflow to reattach to the list. The following method assumes that your new site/location that you are moving the workflow too does not yet have any workflows already attached to any lists on the site. When a workflow is created in a site for the first time, a Tasks list and a Workflow History list are automatically created/used by the workflow. These lists must exist before you can force your migrated workflow to attach to the list.

Step 1 – Create a Blank Workflow
Using SharePoint designer, connect to your destination site
Create a new list-based workflow called “test” (or whatever), attaching it to your migrated list
Create one condition (i.e. if 1 = 1)
Create one action (i.e. add comment “hello”)
Save and publish the workflow
After this step has been completed, your site will now have a Tasks list and Workflow History list.

Step 2 – Get the list ids for your new workflow
The list ids are required to configure your migrated workflow.

Using SharePoint Designer, connect to your destination site
in the left-hand side, in Site Objects, select All Files
in the All Files list, select Workflows
Select your new workflow that you just created (in this example, “test”)
You should see (at least) four files:
text.xoml
test.xoml.rules
test.xoml.wfconfig.xml
test.xsn
Right-click on test.xoml.wfconfig.xml and select Open With, Notepad
Look for the <Association…> tag in the xml:
<Association ListID=”{7DC232FD-4D0B-4F7B-AC72-3D4D6399147C}” StartManually=”true” TaskListID=”{CB551A8B-F1E1-49F9-A8F8-A2C8EDC241C7}” HistoryListID=”{04B19F9D-5A47-4E14-B85E-53FFF06CFA63}” StartOnCreate=”true” StartOnChange=”true”/>
Record the Guid entries for ListID, TaskListID, and HistoryListID

Step 3 – Update the migrated workflow
Still using SharePoint Designer, All Files, Workflows, this time select your migrated workflow
You should again see (at least) four files. Right-click on “your-workflow.xoml.wfconfig.xml” and select Open with SharePoint Designer (As XML)
Find the Association tag and very carefully change the Guids so that your migrated List IDs are the same as the “test” workflow List IDs.
Save the file
Close SharePoint Designer
Open SharePoint Designer again and open your site
Instead of All Files, this time click on Workflows
Select your workflow
Save the workflow
Publish the workflow

Your workflow should now be reassociated with your list on your new site.

Yes! It is!

Monday, October 24, 2011

Sharepoint 2010 - Enable/Disable Ribbon Button with EcmaScript


I show you a detailed example of how to enable and disable a ribbon button according to the current user’s group.

The example uses “EnabledScript” attribute of the CommandUIHandler of the ribbon button.

Then the EnabledScript is built with EcmaScript to find groups and user info.

Lets Start with creating a Ribbon button first.
1. Create a empty project.
2. Deploy it as a Farm solution.
3. Right click on the feature and click “Add feature”.
4. Right click on the project and add a new “Empty Element” item.
5. Next add the below code to add a custom Ribbon button to your document library.

<Elements xmlns=”http://schemas.microsoft.com/sharepoint/” >
<CustomAction
Id=”ButtonForGroupUsersOnly”
Location=”CommandUI.Ribbon”
RegistrationId=”101″
RegistrationType=”List”
Title=”Owners Group Button”>
<CommandUIExtension>
<CommandUIDefinitions>
<CommandUIDefinition
Location=”Ribbon.Library.ViewFormat.Controls._children”>
<Button Id=”Ribbon.Library.ViewFormat.UsersBtn”
Command=”usersBtnCommand”
LabelText=”Group Users Button”
Image32by32=”/_layouts/1033/IMAGES/buttonIcon.jpg”
TemplateAlias=”o1″ />
</CommandUIDefinition>
</CommandUIDefinitions>
<CommandUIHandlers>
<CommandUIHandler
Command=”usersBtnCommand”
CommandAction=”javascript:OwnerBtnscript();“/>
EnabledScript=”javascript:EnablerScript('10,14');” -> Enable Ribbon function here
</CommandUIHandlers>
</CommandUIExtension>
</CustomAction>
//Referencing the Script
<CustomAction
Id=”OwnersButton.Script”
Location=”ScriptLink”
ScriptSrc =”/_layouts/RibbonScripts/CheckUserInGroup.js”/>
</Elements>

The Group Users Button created in the above code will be in the disabled mode on page load. The code in EnablerScript('10,14'); will determine if the current  user is added to the specified group and the button needs to be enabled.
The CustomAction ScriptLink refers to the path of the CheckUserInGroup.js file which contains EnablerScript('10,14'); and other JavaScript functions.


6. Next add a Javascript file in your project “CheckUserInGroup.js” and add it under Layouts -> RibbonScripts folder. Create Layouts folder using Add-> “Sharepoint Layouts Mapped Folder” .

7. The following goes in your CheckUserInGroup.js file
// The below is called by EnabledScript in ribbon button

var idGroupToVerify1 = 10;

var isGroupToVerify2 = 14;
var nameGroup1 = "nameGroup1";
var nameGroup2 = "nameGroup2";
var arrIsInThisGroup = new Array(2);
arrIsInThisGroup["nameGroup1"] = false;
arrIsInThisGroup["nameGroup2"] = false;

ExecuteOrDelayUntilScriptLoaded(InitGroups, "sp.js");
//initialize groups ids for the current user
function InitGroups() {
    CheckUser(idGroupToVerify1, nameGroup1);
   //beacuse of some errors if execute together
    setTimeout("CheckUser(" + idGroupToVerify2 + ", '" + nameGroup2 + "')", 500);
}

function EnablerScript(groupID) {
    if (groupID) {
        var isButtonEnabled = false;
    //groupid comes in string comma separated
   // '10' or '10,14'
        var arrGrId = groupID.split(',');
        var i = 0;
        for (i = 0; i < arrGrId.length; i++) {
            var currGroupID = arrGrId[i];
            if (currGroupID == idGroupToVerify1)
                isButtonEnabled = isButtonEnabled || arrIsInThisGroup[nameGroup1];
            if (currGroupID == idGroupToVerify2)
                isButtonEnabled = isButtonEnabled || arrIsInThisGroup[nameGroup2];
        }
        return isButtonEnabled;
    }
    return true;
}

// The below checks if the user exists in the group
function CheckUser(groupID, isInThisGroup) {

    var context = SP.ClientContext.get_current();
   //I go to parent site if I'm in a subsite!
    var siteColl = context.get_site();
    web = siteColl.get_rootWeb();
    var groupCollection = web.get_siteGroups();
    // Get the Our Group's ID
    var _group = groupCollection.getById(groupID); // ID of the Group that we are checking
    var users = _group.get_users(); // Get all Users of the group
    context.load(_group);
    context.load(users);
    this._users = users;
    this._currentUser = web.get_currentUser(); // Get current user
    this._isInThisGroup = isInThisGroup;
    context.load(this._currentUser);
    context.executeQueryAsync(Function.createDelegate(this, this.CheckUserSucceeded), Function.createDelegate(this, this.failed));
}

//The below Checks  if User is the member of the specified group
function CheckUserSucceeded() {

    if (this._users.get_count() > 0) {
        var _usersEnum = this._users.getEnumerator();
        while (_usersEnum.moveNext()) {
            var user = _usersEnum.get_current();
            if (user.get_loginName() == this._currentUser.get_loginName()) {
                //debugger;
                arrIsInThisGroup[this._isInThisGroup] = true;
            }
        }
    }
}

function failed(sender, args){alert("error");}

8. Next Build and deploy.

Tuesday, October 18, 2011

Blogger - Redirect when you change Blog URL


Recently I changed URL of my blog from giorgioguerrieri.blogspot.com to webdevshareetc.blogspot.com.
I know that my posts linked in facebook, google+, twitter have the old URL, so I search for a solution, a way to redirect post giorgioguerrieri.blogspot.com/2010/eccccc to webdevshareetc.blogspot.com/2010/eccccc, without 404 or other annoying message of Page Not Found.
So, I googled but nothing.

I found my solution with this process.
NOTE: it works if you have a personal site and it's used not for personal site. I use my site for development, so when you navigate to www.giorgioguerrieri.it your browser redirect to my blog.

You have change URL of your blog (giorgioguerrieri to webdevshareetc.blogspot.com)

Create a new blog with URL equals to old URL (new blog giorgioguerrieri.blogspot.com)

Go to Settings in new Blog and set Redirect to your public domain (I've www.giorgioguerrieri.it).

Important! You have to insert URL as "www.yourdomain.com?", yes, with "?".

Save settings and test: when you navigate to your new blog (with old URL), you navigate to your site.

Now, go to ftp of your site and modify index.php (I use php, but you can do this with your language).

function curPageURL() {
 $pageURL = 'http';
 if ($_SERVER["HTTPS"] == "on") {$pageURL .= "s";}
 $pageURL .= "://";
 if ($_SERVER["SERVER_PORT"] != "80") {
  $pageURL .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
 } else {
  $pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
 }
 return $pageURL;
}

  $urlpage = curPageURL();
  $pos = strpos($urlpage, '?/');
  if($pos &gt; 0)
  {
  $urlpage = substr($urlpage, $pos+1);
  $urlpage = "http://webdevshareetc.blogspot.com" . $urlpage;
  }

header("location: " . $urlpage);


If you navigate to "http://blog.blogspot.com/2010/10/post.html", blogger asks you to redirect to "http://www.yourdomain.com?/2010/10/post.html".
Note the "?/...."
Then your index.php takes URL, cut from "?" and compose URL with "http://newurlblo.blogspot.com" + "/2010/10/post.html".
Then redirect (header("location")) to new URL correct page!

You can do other redirect from index.php simply checking if exists "?/" or not in the received URL.
In my script, if there's no "?/", I redirect to home of new Blog.

I hope this help you!





Sharepoint 2010 - Modify Documents Properties with ECMA Script

Finally I found the way to modify document properties with ECMA Script!
It's not an hard work for Document Libraries with no mandatory check out.

Here the script:

function updateDocument(action, docLibId, docId, fieldName, fieldValue) {
    var context = new SP.ClientContext.get_current();  
    var web = context.get_web();  
    var docLibGuid = new SP.Guid(docLibId);  
    var docLib = web.get_lists().getById(docLibGuid);  
    var doc = docLib.getItemById(docId);  
    doc.set_item(fieldName, fieldValue);    
    doc.update();  
    context.executeQueryAsync(Function.createDelegate(this, this.successUpdateDocument), Function.createDelegate(this, this.failed)); 
}
function successUpdateDocument(sender, args) {  
    //reload page if success  
    location.reload(true);
}

But when we set check out mandatory, we have message that we cannot modify document not checked out.
So, I have to check out document in this script.
I change script as shown below:

function updateDocument(action, docLibId, docId, fieldName, fieldValue) {
    var context = new SP.ClientContext.get_current();  
    var web = context.get_web();  
    var docLibGuid = new SP.Guid(docLibId);  
    var docLib = web.get_lists().getById(docLibGuid);  
    var doc = docLib.getItemById(docId);  
 
    //perform check out  
    var file = doc.get_file();  
    context.load(file);  
    file.checkOut();  
 
    //set values  
    doc.set_item(fieldName, fieldValue);    
    doc.update();  
 
    //then check in document    
    file.checkIn();    
    context.executeQueryAsync(Function.createDelegate(this, this.successUpdateDocument), Function.createDelegate(this, this.failed));
}

And it works fine!

Monday, October 17, 2011

SharePoint 2010 WebPart Properties not saving

I lost many time to find the way to save properties of a custom web part in Sharepoint 2010.
All my classes, methods, properties are defined correctly, but when I deploy web part and I try to save props, values were not saved and web part load with his default values.

Then I search for solution and I found that it is due to disposing the current web (SPContext.Current.Web).

I understood that I don't have to dispose SPContext.Current.Web object in my code and neither use using(SPWeb web = SPContext.Current.Web).
The solution is to use directly

SPWeb web = (new SPSite(SPContext.Current.Site.ID)).openweb(SPContext.Current.Web.ID);


to initiate a new web object and then dispose it after using.

The Save button finally saved properties values!

Friday, September 16, 2011

Google+: Le mie Attività - Gadget per il tuo sito

Come promesso. Un po' di smanettamento e via con una mia prima applicazione di esempio delle API per Google+.
Una cosa interessante della chiamata per le attività di un utente è la possibilità di definire nell'URL la funzione javascript che deve essere chiamata al rientro della risposta JSON.
Il nostro json, infatti, non è così immediatamente utilizzabile (e di certo esistono molti più esperti di me, nel settore, ma io l'ho scoperto pian pianino) e ha bisogno di un po' di aiuto, sopratutto quando si tratta di chiamate cross-domain.
Bene. Jquery ci aiuta, sì e no: ho usato le librerie JSONP, che ho linkato direttamente da http://jquery-jsonp.googlecode.com/files/jquery.jsonp-2.1.4.min.js.

La chiamata, così, è composta in questo modo:

$(document).ready(
 function () {
   $.jsonp({
      url: "https://www.googleapis.com/plus/v1/people/100661704413873131754/activities/public?callback=readJSON&key=YOUR_APIKEY"
   });
});

Nell'URL notiamo il parametro "callback" che definisce quale funzione javascript chiamare al ritorno.

La funzione readJSON riceve un oggetto JSON che si può interpretare poi facilmente per scriverne il contenuto.

Il risultato è quello che vedete qui a fianco, nel gadget HTML con il mio codice.

Beh, un primo esempietto :-)
Qui sotto la funzione che ho utilizzato:

function readJSON(json) {
               // ciclo l'array
               var items = json.items;
               for (i = 0; i < items.length; i++) {
                   var item = items[i];
                   var attachments = item.object.attachments;
                   var plusoners = item.object.plusoners.totalItems;
                   var content = '';
                   var img = '';
                   var imgurl = '';
                   var title = '';
                   var text = '';
                   var exturl = '';
                   if (attachments) {
                       for (j = 0; j < attachments.length; j++) {
                           var objAttachment = attachments[j];
                           var objType = objAttachment.objectType;
                           if (objType == 'article') {
                               title = objAttachment.displayName;
                               text = objAttachment.content;
                               exturl = objAttachment.url;
                           }
                           if (objType == 'photo') {
                               imgurl = objAttachment.image.url;
                           }
                       }
                   }
                   else {
                       title = item.title;
                       exturl = item.url;
                   }
                   content = '<div id="' + i + '" class="post">';
                   content += '<h3><a href="' + item.url + '"  title="' + title + '" target="_blank">' + title + ' (' + plusoners + ')</a></h3>';
                   if (imgurl != '')
                       content += '<img src="' + imgurl + '" alt="' + title + '"/>';
                   if(text != '')
                       content +=  text;                  
                   content += '</div>';
                   $('#content').append(content);
               }
           }

Estrarre immagini da PDF o convertire pagine PDF in immagini in C#

E' stata dura. Molto dura. Una nuova sfida: Estrarre immagini da un PDF o convertire una pagina PDF in una immagine. Con C#.

Ho dovuto guardarmi parecchi forum e blog per arrivare ad una soluzione decente, free e con poco sforzo.

Partiamo dal primo caso: Estrazione di una immagine da una pagina di un documento PDF.
Ho utilizzato iTextSharp, che normalmente usiamo per generare PDF, ma che è stato utile per leggere il file e trovare in esso le immagini.

Il metodo qui sotto ritorna lo stream dell'immagine.. perché a me serviva così.
Vediamo il codice (dopo aver referenziato la DLL di iTextSharp):

using iTextSharp.text;
using iTextSharp.text.pdf;

static Stream ExtractImagesFromPDF(string sourcePdf)
{
  System.IO.MemoryStream streamImage = new MemoryStream();
  // NOTE: This will only get the first image it finds per page.
  PdfReader pdf = new PdfReader(sourcePdf);
  RandomAccessFileOrArray raf = new iTextSharp.text.pdf.RandomAccessFileOrArray(sourcePdf);
  int numbOfPages = 1; //or you can set = pdf.NumberOfPages and do it foreach page;
  try
  {
    PdfDictionary pg = pdf.GetPageN(numbOfPages);
    PdfDictionary res = (PdfDictionary)PdfReader.GetPdfObject(pg.Get(PdfName.RESOURCES));
    PdfDictionary xobj = (PdfDictionary)PdfReader.GetPdfObject(res.Get(PdfName.XOBJECT));

    if (xobj != null)
    {
      foreach (PdfName name in xobj.Keys)
      {
        PdfObject obj = xobj.Get(name);
        if (obj.IsIndirect())
        {
          PdfDictionary tg = (PdfDictionary)PdfReader.GetPdfObject(obj);
          PdfName type = (PdfName)PdfReader.GetPdfObject(tg.Get(PdfName.SUBTYPE));
          if (PdfName.IMAGE.Equals(type))
          {
            int XrefIndex = Convert.ToInt32(((PRIndirectReference)obj).Number.ToString(System.Globalization.CultureInfo.InvariantCulture));
            PdfObject pdfObj = pdf.GetPdfObject(XrefIndex);
            PdfStream pdfStrem = (PdfStream)pdfObj;
            byte[] bytes = PdfReader.GetStreamBytesRaw((PRStream)pdfStrem);
            if ((bytes != null))
            {
              streamImage = new System.IO.MemoryStream(bytes);
              break;
            }
          }
       }
    }
   }
 }
 catch(Exception ex)
 { 
   Console.Write("Error extracting image from PDF : " + ex.Message);
 } 
 finally
 {
   pdf.Close();
 }
 return streamImage;
}

Quello che fa non è altro che leggere la prima pagina (ma è possibile ciclare e salvarsi un array di immagini) ed estrarre l'oggetto di tipo IMAGE del PDF.
Ma, ho incontrato un po' di difficoltà.
Innanzitutto, si presuppone che il PDF sia "ben fatto", quindi abbia effettivamente la struttura che leggiamo.
Questa struttura, ad es., viene correttamente generata dal "Save as PDF" di MS Office 2007/2010.
Purtroppo, però, alcuni PDF non sono così semplici da trattare.
Ad es., ho un PDF che ha una immagine grande: probabilmente generato da "Save as PDF" da Photoshop: non ha quindi la struttura letta comodamente con iTextSharp (ovvero è nullo l'XObject del PDF).

Allora, passiamo al secondo metodo: Convertire una pagina del PDF in una immagine.
Per questo progettino, ho utilizzato le librerie e il codice di Cyotek: http://cyotek.com/blog/convert-a-pdf-into-a-series-of-images-using-csharp-and-ghostscript

I passi da seguire sono abbastanza ben descritti. Li ripeto come memorandum:
- Download e install dei GhostScript http://www.ghostscript.com/ : copiare la DLL da copiare nella root dell'applicazione o nella bin.
- Download dei due progetti forniti da Cyotek: Cyotek.GhostScript (classe che espone i metodi di interfaccia con GS) e Cyoteck.GhostScript.PDFConversion (classe che espone i metodi di conversione).
- Estrarre i due progetti, compilarli e aggiungere le DLL nelle referenze del nostro progetto.
- E ora due righe di codice per utilizzarlo:

static System.Drawing.Bitmap ConvertPdfToImage(string sourcePdf)
{
    MemoryStream streamImage = new MemoryStream();
    //conversione for gs
    Pdf2ImageSettings settings;
    settings = new Pdf2ImageSettings();
    settings.AntiAliasMode = AntiAliasMode.High;
    settings.Dpi = 300;
    settings.GridFitMode = GridFitMode.Topological;
    settings.ImageFormat = ImageFormat.Jpeg;
    settings.TrimMode = PdfTrimMode.CropBox;

    System.Drawing.Bitmap img = new Pdf2Image(sourcePdf, settings).GetImage()
}

Bene.
I due metodi insieme mi hanno permesso di ottenere quanto cercavo.
E il cliente è contento. Spero!


Thursday, September 15, 2011

Getting Started on the Google+ API

E ora sarà dura battaglia a facebook!
Google apre le sue prime API per utilizzare la piattaforma Google+.
E' cosa dell'ultimissima ora, mi è appena arrivata comunicazione proprio da G!

Due i punti di partenza.
Un post su http://googleplusplatform.blogspot.com/, con descrizione delle prime (ridotte) funzionalità.
E il nuovo sito per gli sviluppatori per Google+ Platform: https://developers.google.com/+/

Ma veniamo a cosa possiamo fare, per adesso, con queste prime API.
Traduco, più o meno liberamente, alcuni passi base dal post ufficiale.

Let ' S Go Public!

Questo rilascio iniziale di API è focalizzato solo sui dati pubblici: consente di leggere le informazioni che le persone hanno condiviso pubblicamente su Google +.
Ad esempio, se si desidera ottenere le informazioni del mio profilo, è possibile utilizzare il metodo people.get inviando una richiesta HTTP, con l'id del mio utente:

GET https://www.googleapis.com/plus/v1/people/100661704413873131754?key=yourAPIKey

La riposta è un output JSON:

{
 "kind": "plus#person",
 "id": "100661704413873131754",
 "displayName": "Giorgio Guerrieri",
 "tagline": "Chi cerca trova",
 "gender": "male",
 "aboutMe": "",
 "url": "https://plus.google.com/100661704413873131754",
 "image": {
  "url": "https://lh6.googleusercontent.com/-Nx4ziEjmuQM/AAAAAAAAAAI/AAAAAAAAAAA/FVoLqNofkR8/photo.jpg"
 },
 "urls": [
  {
   "value": "http://giorgioguerrieri.blogspot.com"
  },
  {
   "value": "http://www.flickr.com/photos/gguerrieri/"
  },
  {
   "value": "http://www.facebook.com/giorgioguerrieri1977"
  },
  {
   "value": "http://twitter.com/ggorgos"
  },
  {
   "value": "http://www.flickr.com/people/gguerrieri/"
  },
  {
   "value": "https://plus.google.com/100661704413873131754",
   "type": "profile"
  },
  {
   "value": "https://www.googleapis.com/plus/v1/people/100661704413873131754",
   "type": "json"
  }
 ],
 "organizations": [
  {
   "name": "Liceo Scientifico Leonardo Da Vinci, Genova",
   "type": "school"
  },
  {
   "name": "CAP spa",
   "title": "Developer",
   "type": "work"
  }
 ],
 "placesLived": [
  {
   "value": "Genova",
   "primary": true
  }
 ]
}

Analogamente, è possibile ottenere un elenco dei post più recenti (immessi con visibilità Pubblica), utilizzando il metodo activities.list

GET https://www.googleapis.com/plus/v1/people/100661704413873131754/activities/public?key=yourAPIKey

Facile facile. Così sembra.

Ad ogni modo, non è molto diverso da quello che siamo abituati a fare con Google Maps, per esempio.
E, allo stesso modo, serve un API Key, che si può generare da questo sito https://code.google.com/apis/console/.

Interessante, infine, la sezione Download del sito https://developers.google.com/+/downloads che mette a disposizione progetti demo e sopratutto librerie per molti linguaggi comunemente utilizzati per il web.
(Manco a dirlo, la versione .NET è in beta, eh eh)

Beh, resisto alla tentazione di buttarmi su queste API e vado a nanna che è meglio!

Monday, August 29, 2011

Sharepoint 2010 - Custom Action: visibilità secondo permessi utente

In un mio post ho spiegato come aggiungere bottoni al Ribbon, contestuali sull'item, per disporre di Azioni personalizzate (Sharepoint 2010 - Aggiungere bottone al Ribbon con Sharepoint Designer 2010).
Un punto aperto di quel post è come rendere questi bottoni visibili solo a chi ne ha i permessi.
Eccomi quindi ad aggiornare il Blog con questa interessante possibilità.

L'opzione Rights Mask o Maschera di diritti ci permette di soddisfare questo requisito.
I valori disponibili possono essere trovati su MSDN, ma per nostra comodità li scrivo qui sotto.

È possibile inserire più valori nel campo Rights Mask separati da punti e virgola.


Ed ecco qui i valori permessi

List/document perms Mask
ViewListItems1
AddListItems2
EditListItems4
DeleteListItems8
ApproveItems16
OpenItems32
ViewVersions64
DeleteVersions128
CancelCheckout256
ManagePersonalViews512
ManageLists2,048
ViewFormPages4,096
Web level perms
Open65,536
ViewPages131,072
AddAndCustomizePages262,144
ApplyThemeAndBorder524,288
ApplyStyleSheets1,048,576
ViewUsageData2,097,152
CreateSSCSite4,194,314
ManageSubwebs8,388,608
CreateGroups16,777,216
ManagePermissions33,554,432
BrowseDirectories67,108,864
BrowseUserInfo134,217,728
AddDelPrivateWebParts268,435,456
UpdatePersonalWebParts536,870,912
ManageWeb1,073,741,824
UseRemoteAPIs137,438,953,472
ManageAlerts274,877,906,944
CreateAlerts549,755,813,888
EditMyUserInfo1,099,511,627,776
special perms
EnumeratePermissions 4,611,686,018,427,380,000

Friday, August 26, 2011

Applicazioni per Android: Svilupparle con Appsgeyser


Bentornati dalle ferie!
Sapevo che comprare un smartphone con Android mi avrebbe fatto pensare che prima o poi, in qualche modo, sarei riuscito a sviluppare un'applicazione!
Beh, non è facile devo dire...
Ho già provato ad installare l'SDK, per capire che strumenti ci vogliono, ecc...

Troppa fatica e troppo tempo da dedicare, in un momento in cui tempo non c'è, o se c'è, quello che manca è la voglia di mettersi davanti al pc, invece che stare all'aria aperta!

Così, ho cercato e ho trovato il modo più semplice: www.appsgeyser.com!



Un servizio, semplice e gratuito, che permette di implementare una applicazione per Android online!

Detta così, lo capisco, sembra sbalorditivo! Ma quello che succede è presto fatto.
L'applicazione in questione fa uso del browser dell'apparecchio, e tramite questo raggiunge widget o pagine web Mobile.
Però, installa sul telefonino l'applicazione, con tanto di icona. Senza AD (a meno di non utilizzarla per distribuzione on line tramite Market o tramite il loro programma di AD).

Quindi: ho creato tre pagine web per mobile (html, jquery, lettura di rss, in php), ho configurato l'applicazione tramite il loro sito e via!

Devo dire che dopo un po' di click si pianta o diventa lenta, quindi qualcosa non funziona a dovere...
Ma il trucco è lo stesso utilizzato da Microsoft, dal sito stesso (e quindi anche da me): è una versione BETA.

Ecco il link al mio prodotto, naturalmente, di colore Rosso blu ;-)


Wednesday, August 3, 2011

Sharepoint 2007 - Exception from HRESULT: 0x8102009B

Per la serie "Messaggi di Errore Parlanti", ecco la spiegazione dell'errore indicato qui sotto:

Microsoft.SharePoint.SPException: Exception from HRESULT: 0x8102009B System.Runtime.InteropServices.COMException (0x8102009B): Exception from HRESULT: 0x8102009B
at Microsoft.SharePoint.Library.SPRequestInternalClass.AddWorkflowToListItem(String bstrUrl, String bstrListName, Int32 …..

Bene: si sta tentando di avviare una nuova istanza di un workflow, ma quel workflow è già avviato oppure è in errore.

Rimuovere le istanze di workflow, interrompere il flusso attivo e .. riprova sarai più fortunato.

Wednesday, July 13, 2011

Android SDK - Install su Windows 7 32bit

Oggi ho provato ad installare l'Android SDK sul mio pc Windows7 a 32bit.
Il primo messaggio dice "Java SE development Kit (JDK) not found": giusto, non c'è.
Seguo le indicazioni, scarico e installo JDK.

Riprovo Android SDK e ancora "Java SE development Kit (JDK) not found"...

Ho trovato su un forum che il problema si presenta sia con Windows7 32 bit che con Windows XP SP3.

La soluzione:
Nella schermata del messaggio "JDK not found", clicca su BACK e poi ancora NEXT ed ecco che si può continuare..

Mah...

Monday, July 11, 2011

Microsoft Dynamics CRM 2011 - DeletionStateCode Missing


La nuova versione CRM 2011 porta in sé un certo numero di modifiche, di cui più o meno si è parlato.
Una di cui non si è detto quasi niente non è neppure così banale, per chi sviluppa personalizzazioni. 


DeletionStateCode
In CRM 4.0, esiste la colonna DeletionStateCode, su ogni entità, a livello di database, e che ci dice quando un record è stato eliminato. I suoi valori sono: 
- 0 indica il record è in stato attivo nel database, 
- 2 il record è stato contrassegnato per la rimozione, quando il Deletion Job verrà eseguito
- 1 il record non è utilizzato. 
Schedulando il servizio di Deletion, vengono rimossi dalle tabelle tutti i record con DeletionStateCode = 2.
Per fortuna o purtroppo, il servizio spesso non parte o dà problemi o lascia qualche record.

Quando sviluppiamo personalizzazioni in CRM, bisogna tener conto della colonna DeletionStateCode dei record, ad esempio per escludere dalle query quelli che sono "in cancellazione" e non più validi.
Inoltre, la proprietà rende facile il recupero di dati erroneamente cancellati, o determinare chi li ha cancellati.


CRM2011
Nel 2011 CRM, 'DeletionStateCode' non esiste più... 
Quando si elimina un record, si elimina e basta. 
Non non c'è alcuna traccia del record cancellato. 
Questo significa che non occorre includere il controllo del DeletionStateCode nella vostra personalizzazione. 

Il problema è che, se si desidera tornare indietro e recuperare i vecchi record, è necessario eseguire un ripristino di una versione di backup del database. E se volete vedere chi ha cancellato cosa, è necessario utilizzare un audit logging.


Sviluppo personalizzazioni
L'impatto sul nuovo è zero, ma una migrazione porta alcuni problemi e accorgimenti da attuare.Sulle personalizzazioni, occorre togliere ogni riferimento al DeletionStateCode dal codice, dai report o dalle query.


Migrazioni
Inoltre, se avete pianificato un aggiornamento da CRM 4.0 al CRM 2011, il consiglio è quello di eseguire il servizio di Deletion manualmente, prima di aggiornare, e controllare che tutti i record con DeletionStateCode = 2 siano stati effettivamente eliminati dal sistema. 
Se rimangono alcuni record con lo stato a 2, quando il sistema viene aggiornato, quando si accede in CRM 2011, magicamente riappaiono come record attivi!!

Monday, July 4, 2011

Nexus Contraptions: gioco dedicato al Nexus S

Smanettare con il proprio nuovo Nexus S...
Google sembra aver sempre voglia di giocare. Non stupisce quest'altra novità: un gioco dedicato al suo ultimo smartphone.
Lo scopo del gioco è costruire un Nexus S utilizzando vari strumenti per spostare nell’imbuto le applicazioni a forma di sfere.



Tutti i livelli sono cronometrati e, alla fine del gioco, comparirà il punteggio totale e un bel Nexus S come premio.

Il gioco è realizzato il flash ed è disponibile sul sito Nexus e i suoi Marchingegni.

Buon divertimento!
Anche se preferisco giocare con quello vero ;-)

Thursday, June 30, 2011

+1 (plus one) Il nuovo bottone di Google sul tuo Sito

Dopo aver un po' cambiato tutti noi, Google sta cambiando se stesso..
Di recente, ha cambiato aspetto, il tema delle sue pagine e anche il metodo di ricerca...
Pure un nuovo Social Network (Google+), giusto per fare un po' di movimento.

Ma prima ancora, ha lanciato un nuovo bottone: il +1!
E non è assurdo pensare che il "plus one" sia venuto a predisporre Google+... anzi.

Il +1 è già integrato in molti siti e i risultati della ricerca di Google permettono di dare un senso e condividere quello che abbiamo cercato. E trovato.

Ma, come Facebook insegna, quel bottoncino, volete mica lasciarlo solo sui siti Google?

Ovviamente, potete andare a vedere come si fa, direttamente da Google.
Ma mi piace scrivere due righe su come sia altrettanto semplice del "Like" di Facebook.
Anzi, forse è più facile ancora: richiede solo due righe di codice.
Una nell'head o nel body e l'altra nel posto in cui si desidera inserire il +1.

<script type="text/javascript" src="http://apis.google.com/js/plusone.js"></script>
<g:plusone></g:plusone>


Ovviamente, esistono alcune configurazioni per la personalizzazione del bottone.
La dimensione, ad esempio: è possibile scegliere tra piccolo (altezza: 15px), medio (20px), alto (60px) o altro ancora.

<g:plusone size="small"></g:plusone>

Possiamo definire un URL diverso per cui far cliccare +1.
Semplicemente con un attributo del tag e l'url preferito:

<g:plusone href="http://codewithdesign.com"></g:plusone>

Mi piace!
+1!

Per chi non lo sapesse, il +1 ricliccato diviene "-1".. non è proprio intuitivo...
La mia gatta continuava a cliccare +1, pensando di essere arrivata ormai a 25.. ma era ferma a.. a quanto? Indovinatelo!

Google+ è arrivato e... ci sto già dentro!

Dopo un primo tentativo con Google Wave, miseramente fallito, direi, ora Google ci riprova!
Ecco presentato in test, a inviti il nuovo Social Network targato Big G


Il privilegio di entrarci era fino a poco fa ad esclusiva di pochi eletti, ma ora è libero, o almeno.. un mio car(l)o amico mi ha segnalato il tutto e ora...

Già. E ora?
E ora si va alla scoperta di un'altra storia.
Riusciremo a stare dietro a tutti i nostri social-posti?

La cosa più interessante, che mi hanno fatto notare, è questa, probabilmente il futuro possibile del web, a prescindere dalle decine di social network in cui saremo iscritti:

<!DOCTYPE html>



Monday, June 27, 2011

Sharepoint 2010 - Aggiungere bottone al Ribbon : aprire nuovo Documento da Template

Ecco una cosa interessante.
Aggiungere nuovi bottoni al Ribbon di una lista, in Sharepoint 2010, è abbastanza semplice. Ho già scritto come farlo con ancora più facilità, tramite lo Sharepoint Designer 2010 (clicca qui).

Ora, vediamo come aggiungere una azione personalizzata al bottone in modo che replichi le funzionalità di bottoni già esistenti.
Lo scenario è questo.



Siamo in una Document Library. Cliccando sulla lista, viene visualizzato il Ribbon della lista.
Nella sezione Documents, a sinistra, abbiamo il bottone New. Se abbiamo più Content Type (e template) per i Documenti, vedremo una tendina con la scelta del nuovo Documento da creare.
Se configurato per l'esecuzione sul Client dell'utente, al click su, ad es, New Template1, viene avviato MS Word con il template in oggetto.

Ma se volessimo creare un nuovo bottone sul Ribbon che faccia da solo questo?
In altre parole: non voglio cliccare su New, per poi selezionare dalla tendina quello che serve. Voglio cliccare su un bottone del Ribbon "Crea Nuovo Template1" che mi apra subito Word con quel template.
Oppure, questo bottone, lo voglio sul Ribbon di una lista della pagina, non sulla Document Library.

Ho dovuto smontare un po' il Javascript del Ribbon della lista e della Doc. Library.

Ecco come fare.
Aggiungo il bottone tramite lo Sharepoint Designer 2010 (Sito, Seleziono lista, aggiungo Azione Personalizzata, ecc). Come azione, scelgo Apri URL e nel campo inserisco le righe seguenti:

javascript:createNewDocumentWithProgID('http:\u002f\u002fDOMINIOSP\u002fsites\u002fSITO\u002fDOCUMENTLIBRARYNAME\u002fForms\u002fCONTENTTYPE\u002fTEMPLATE1.docx', 'http:\u002f\u002fDOMINIOSP\u002fsites\u002fSITO\u002fDOCUMENTLIBRARYNAME', 'SharePoint.OpenDocuments', false)

Ovviamente, un po' bisogna lavorare: occorre trovare l'indirizzo corretto del template da utilizzare e della lista in cui salvare il Documento da Word.
Nota: trasformare l'URL come indicato, cioè con "\u002f" al posto di "/".

Questo è solo uno dei Javascript da poter utilizzare, ma si possono scovare altre funzioni e inserirle nelle Azioni Personalizzate.

Buon lavoro!

Sharepoint 2010 - Aggiungere bottone al Ribbon con Sharepoint Designer 2010

Vediamo come aggiungere un bottone personalizzato al Ribbon di una lista o document library, utilizzando solo lo Sharepoint Designer 2010.

Apriamo il sito con SPD2010, nelle liste, selezionamo la lista che vogliamo utilizzare.
A fondo pagina, vediamo il blocco Azioni Personalizzate.


Non clicchiamo su Nuovo, ma facciamo un clic con il mouse all'interno del blocco e dal menu contestuale, scegliamo Azione Personalizzata.
Vediamo un menu a discesa con diverse opzioni:


La traduzione italiana pessima rende oscura l'interpretazione delle voci...
Provo a spiegarle.

Menu voci elenco: il bottone è visualizzato nel menu a tendina sull'item
Visualizza barra multifunzione modulo: il bottone è visualizzato nella scheda "View properties"
Modifica barra multifunzione modulo: il bottone è visualizzato nella scheda "Edit properties"
Nuova barra multifunzione modulo: il bottone è visualizzato nella scheda "New item"
Visualizza barra multifunzione: il bottone è visualizzato nel Ribbon contestuale per l'item selezionato

Farò l'esempio solo per il Ribbon, quindi selezioniamo la voce "Visualizza barra multifunzione".
Si apre la finestra di dettaglio per l'Azione personalizzata.


Come si vede propone tre azioni:

  • Passa al modulo: propone il reindirizzamento al modulo specificato (DispForm, EditForm, ecc)
  • Avvia Flusso di Lavoro: propone l'elenco dei WF ad avvio manuale associati alla lista
  • Passa all'URL: permette di inserire URL di redirect, ma anche e sopratutto chiamate Javascript!

Utilizziamo quest'ultimo campo, in cui scopriamo che possiamo definire un parametro associato all'Id dell'Item selezionato: è il SelectedItemId, ed è strettamente Case Sensitive!

javascript: alert('Custom Action on Item {SelectedItemId}');

Definiamo, inoltre, altre informazioni, tra cui ad es, l'icona da utilizzare.


Altre opzioni avanzate sono da studiare per bene, prima di utilizzare (ad es, Scheda.ID gruppo).
La maschera per i diritti dovrebbe consentire la definizione del livello di visibilità del bottone da parte di alcuni utenti.
L'ultima opzione "Numero di sequenza" è l'ordinamento all'interno del blocco Action del Ribbon.
Clicchiamo OK e il bottone viene salvato subito ed è subito visibile.

Apriamo il sito e selezioniamo la lista: vediamo il ribbon con il nuovo bottone, ma non cliccabile, poiché non abbiamo ancora selezionato un Item.


Selezioniamo l'item e vediamo il comportamento del Ribbon:

Il bottone è ora cliccabile! E che facciamo? Lo clicchiamo?


Notiamo alcune cose importanti.
Possiamo utilizzare il SelectedItemId e passarlo ad una application page, ad esempio. E tramite Object Model consumare l'informazione sull'item.
Possiamo utilizzare chamate Javascript in quel bottone!
Così abbiamo a disposizione tutte le funzioni di Sharepoint e richiamare quelle che interessano!

Vedrete in altro post, come utilizzare ad es. il comando per aprire MS Word con un template selezionato.

Buon lavoro!

Sharepoint 2007 - Deploy in produzione con Visual Studio 2008

Dopo aver partecipato a corsi di Sharepoint 2010 e visto quanto sia (relativamente ok...) semplice eseguire il Deploy di soluzioni dall'ambiente di test all'ambiente di produzione, mi sono scontrato con la realtà di Sharepoint 2007... Il deploy di una web part custom, implementata con Visual Studio 2008.

Lo sviluppo è andato liscio, senza problemi.
Il deploy in ambiente di test, ancora più semplice: tasto destro sul progetto, click su Deploy e via.

Ora è giunto il momento del deploy in produzione.. Come fare?

Diversi forum mi propongono soluzione a detta loro "semplici", con creazione di un progetto CAB per generare un package da copiare in produzione e poi deploy con stsadm. Non è così facile.

Almeno, io ho incontrato difficoltà per due motivi:

- o riconosce il file come CAB quindi vuole addpackage anziché addsolution
- o (se rinominato in .wsp) non vuole addsolution
- o mi dice che manca il manifest.xml (pur essendoci)

Allora, ho seguito una mia strada.

Da Visual Studio (in ambiente test), modifico il codice con eventuali puntamenti a siti o liste di test, con i Guid o nomi di produzione.
Sempre su test, eseguo un Deploy dell'applicazione. Effettua la compilazione, esegue il Deploy.
Di certo, su test, la web part non funziona (dà Errore - File Not Found, perché cerca liste o siti che non esistono su quella macchina).
Ma a me, quello che interessa è il WSP con cui esegue questo Deploy!

Apro la folder bin (in Debug o Release) e vedo il mio package WSP.
Lo copio in una folder su Produzione.

A questo punto, semplicemente, mi posiziono in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN e:

stsadm -o addsolution filename C:\PERCORSO\NOMESOLUZIONE.wsp

O, se già in produzione:

stsadm -o upgradesolution -name NOMESOLUZIONEDACENTRALADMIN.wsp -filename C:\PERCORSO\NOMESOLUZIONE.wsp -immediate -allowGacDeployment

E voilà!
Moolto pià semplice del previsto (per così dire...)

Buoni Deploy!

Sunday, June 26, 2011

LyteBox sulle immagini inserite in Blogger

Ovviamente non l'ho scoperto io né tantomeno me lo sono inventato.

Copio e incollo un riassunto delle istruzioni per l'effetto tipo Lightbox per le immagini inserite in questo Blog!

Andare su Layout > Modifica HTML e, dopo aver salvato il modello completo, cercate la riga </head>, quindi, immediatamente sopra incollateci il seguente codice (ovviamente potete scaricare i file necessari e metterli dove volete):


<!-- EFFETTO LYTEBOX INIZIO -->
<link href='https://sites.google.com/site/ideepercomputeredinternet/script-1/lytebox-stile-2.css' media='screen' rel='stylesheet' type='text/css'/>
<script src='http://sites.google.com/site/ideepercomputeredinternet/script-menu/lytebox-script.js' type='text/javascript'/>
<!-- EFFETTO LYTEBOX FINE -->

Salvate il modello e la personalizzazione è completata.
Per attivarla bisogna operare sul codice delle foto inserite. Si possono ottenere effetti su una singola immagine, su un'intera galleria e creare degli slideshow attraverso l'editor di Blogger.


Vediamo per la singole immagine come fare.
Se si posta l'immagine con l'editor di Blogger possiamo farlo selezionandola dall'hard-disk o dal web, personalmente preferisco la seconda ipotesi ma per l'effetto è indifferente.
Se si carica dal computer, bisogna passare alla modalità HTML andando su Modifica HTML. Nel codice relativo all'immagine che abbiamo caricato: individuare il tag <a> e subito dopo, inserire l'espressione rel="lytebox".

<a rel="lytebox" href="....

Se vogliamo una gallery, dobbiamo innanzitutto sceglierne il nome, ad es "tramonti".
Poi si fanno le modifiche come sopra, con l'attenzione di mettere l'espressione rel="lytebox[tramonti]".


E per le gallery:


Sharepoint 2007 - Incrementare il limite di peso del Site Template

Il limite impostato di default da Sharepoint 2007 per l'operazione "Save as Template" è di 10 Mb.

Questo valore può essere cambiato utilizzando il comando stsadm.
Posizionarsi nella folder 12 di MOSS2007 (in Drive:\\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN\) e avviare il comando seguente:

stsadm -o setproperty -propertyname max-template-document-size -propertyvalue 40000000

Nell'esempio, il comando incrementa il limite e 40Mb.

Sharepoint 2010 - Cambiare Lingua al Titolo delle Web Part

Sharepoint 2010 dà la possibilità di implementare la localizzazione dei siti (e dei suoi elementi), grazie alla Multilanguage User Interface.

Purtroppo, come ben documentato da MS stessa (http://technet.microsoft.com/it-it/library/ff805062.aspx#Section7), non tutti gli elementi di un sito possono avere al seguito le traduzioni.

Una possibilità è quella di utilizzare un controllo client che ci permetta di cambiare lingua al titolo delle web part.
Analizzando il codice della pagina prodotta da Sharepoint, ho individuato i tag necessari ad identificare il titolo della web part.


La class ms-WPTitle dell'elemento h3 definisce il titolo della web part. Al suo interno, dobbiamo cercare il tag "span" che contiene il titolo. Generalmente è il primo dei due, senza id e, ovviaemente, non vuoto. Bene, sappiamo dove prendere il titolo.


Definiamo ora un modo con cui l'utente possa essere autonomo nella gestione dei titoli in multilingua.
L'idea è quella di utilizzare il campo Title delle proprietà della web part e chiedere all'utente di scrivere i titoli in lingua, separati da pipe ('|').

Title = "English title|French title|Italian title

Il js implementato controlla che siano presenti tutte le lingue (in questo caso, tre lingue) e se esistono tutte, seleziona quella da utilizzare nella visualizzazione, altrimenti inserisce come titolo quello che c'è..

Inoltre, lo script gira su tutte le web part della pagina, ovvero cerca i tag H3 con classe "ms-WPTitle" e ne ricerca il tag span con il titolo.

Utilizzando uno script con jquery, ho implementato il codice :

$(document).ready(function () {
var lcid = L_Menu_LCID;
var arrayH3 = $(".ms-WPTitle");
$.each(arrayH3, function () {
var title = $(this).children("a").children("nobr").children("span").first();
var arrTitle = title.text().split('|');
if (arrTitle.length == 3) {
if (lcid == 1036) { //french
title.text(arrTitle[1]);
}
else if (lcid == 1040) { //italian
title.text(arrTitle[2]);
}
else { //english / default
title.text(arrTitle[0]);
}
}
});
});

L_MENU_LCID è una variabile di Sharepoint che definisce l'identificativo della lingua corrente utilizzata dall'utente.
Lo script può essere inserito a fondo pagina in una Content Editor Web Part (come HTML), con in testa il richiamo alle librerie jquery.


Se le lingue sono in numero diverso, occorre modificare il codice utilizzando il numero e gli identificativi corretti per la lingua.

Spero vi sia utile!

Saturday, June 25, 2011

Sharepoint 2007 - When use Lookup Columns instead Choice Columns

There are a number of ways to store options for a field that requires a group of pre-selected options, a drop down list as it were. I always thought that defining a column of the type choice is the way to go but recently discovered that in a couple of areas that creating a list and then using that as a look up is actually a better solution.

When to use a Choice Column

You should use a choice column, pictured below, in the following circumstances:
1) Only site administrators should be able to add/remove/edit options
2) You don’t have that many options to choose from (maximum ten) and users will usually only select at most 5 options.
3) The options rarely change

This is the usual way to go in a SharePoint environment but it can cause many issues. For starters a choice column can only store 255 characters (SharePoint in built field limit) so if your users are choosing 10 options each of 26 characters (see below) then they wont be able to. This is because the choice fied actually stores all the selected options as a semi-colon delimited string, which also causes some issues with the Data View Web Part when attempting to render the values.



Another reason is that if you have many, many options the interface will look horrendous. In my case we had something like 250 possible options for 6 different field which meant that a user would scroll themselves silly when trying to enter metadata.

Finally the options would change often, new options would have to be added but we didn’t want to give these people administrator privileges or have them harass IT for a simple task… so what was the answer? Use a lookup column instead.

The Lookup Column

The lookup column is simply another type of column within SharePoint that looks up a list to get its options. Seems simple enough but it has certain advantages that in this case saved us a whole lot of time

To implement a look up column in SharePoint you need to do two simple things things:

1) Create a list that will store the data that you want to look up and
2) Create the look up column to be used.


Lookup Column Advantages

The LookUp column has the following characteristics which were very useful in our case:

Can be easily managed without special permissions

If you want to give your users the power to add, edit and delete options then since the values for the lookup are stored in a list they can have normal access rights without giving them the special permissions required to manged fields in Sites or Site Collections.

Can store an unlimited amount of options

The Lookup list, unlike a choice field, can hold an unlimited amount of options that are selected if you choose the “Allow Multiple Values” and “Allow unlimited length in document libraries” options. This is a really good way to get around the pesky 255 character limit that you encounter whilst using a choice field.



Clean Interface

This is one thing that the Look Up list really prevails in that instead of using a series of select boxes, which gets unwieldly when you have many options, the Look Up list is rendered as two lists where the user can select values, much cleaner! You can also change the size of the boxes pretty easily using JQuery or SharePoint Designer, a good link is here.




Conclusion

So as you can see there are many ways to skin the cat in SharePoint and the choice between a look up list and a normal choice field can produce some interesting results and challenges. I hope that this article gave you some insights into how to solve these issues.