Showing posts with label jQuery. Show all posts
Showing posts with label jQuery. Show all posts

Tuesday, January 26, 2010

SharePoint MaskedEdit Field Control

A control that I used over and over in the good old days of VB6 was the masked edit control. I recently came across a jQuery plugin for a masked edit control and wanted to implement it as a custom SharePoint field. Download the code here

What you will need. 
jQuery
jQuery Masked Edit Plugin
WSPBuilder

This is masked edit field as seen when adding it to a list. I've defined a small number of masks, you can add more! This interface is built with the next two code blocks. The first code block is the user control that contains the HTML and the server controls. The second code block is the code behind for the user control. 

<%@ Control Language="C#" Inherits="MaskedEditField.jQueryMaskedEditFieldEditor, MaskedEditField, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ab6ae01ba130938e"    compilationMode="Always" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" src="~/_controltemplates/InputFormControl.ascx" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %>

<wssuc:InputFormControl runat="server" LabelText="Input Masks Explained">
    <Template_Control>
        <div>A mask is defined by a format made up of mask literals and mask definitions.
        Any character not in the definitions list below is considered a mask literal.
        Mask literals will be automatically entered for the user as they type and will
         not be able to be removed by the user.
        <ul>
            <li>a - Represents an alpha character (A-Z,a-z)</li>
            <li>9 - Represents a numeric character (0-9)</li>
            <li>* - Represents an alphanumeric character (A-Z,a-z,0-9)</li>
        </ul>  
        </div>
    </Template_Control>
</wssuc:InputFormControl>

<wssuc:InputFormControl runat="server" LabelText="Input Masks">
    <Template_Control>
        <table>
            <tr>
                <td><asp:RadioButton Text="Phone Number" GroupName="MaskedEdit" ID="rPhone" runat="server"></asp:RadioButton></td>
                <td><asp:TextBox Enabled="false" ID="txtPhone" runat="server">(999) 999-9999</asp:TextBox></td>
            </tr>
            <tr>
                <td><asp:RadioButton Text="SSN" GroupName="MaskedEdit" ID="rSSN" runat="server"></asp:RadioButton></td>
                <td><asp:TextBox Enabled="false" ID="txtSSN" runat="server">999-99-9999</asp:TextBox></td>
            </tr>
            <tr>
                <td><asp:RadioButton Text="Zip Code + 4" GroupName="MaskedEdit" ID="rZip4" runat="server"></asp:RadioButton></td>
                <td><asp:TextBox Enabled="false" ID="txtZip4" runat="server">99999-9999</asp:TextBox></td>
            </tr>
            <tr>
                <td><asp:RadioButton Text="Zip Code" GroupName="MaskedEdit" ID="rZip" runat="server"></asp:RadioButton></td>
                <td><asp:TextBox Enabled="false" ID="txtZip" runat="server">99999</asp:TextBox></td>
            </tr>
            <tr>
                <td><asp:RadioButton Text="Custom" GroupName="MaskedEdit" ID="rCustom" runat="server"></asp:RadioButton></td>
                <td><asp:TextBox Enabled="true" ID="txtCustom" runat="server"></asp:TextBox></td>
            </tr>
        </table>
    </Template_Control>
</wssuc:InputFormControl>



User Control Code Behind
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;

namespace MaskedEditField
{
    public class jQueryMaskedEditFieldEditor : UserControl, IFieldEditor
    {
        // Fields
        protected RadioButton rPhone;
        protected RadioButton rSSN;
        protected RadioButton rZip;
        protected RadioButton rZip4;
        protected RadioButton rCustom;
        protected TextBox txtPhone;
        protected TextBox txtSSN;
        protected TextBox txtZip;
        protected TextBox txtZip4;
        protected TextBox txtCustom;
        private jQueryMaskedEdit fldjQueryMaskedEdit;

        public void InitializeWithField(SPField field)
        {
            this.fldjQueryMaskedEdit = field as jQueryMaskedEdit;

            if (this.Page.IsPostBack)
            {
                return;
            }

            //when modifying a field check the correct radio button
            if (field != null)
            {
                string prop = fldjQueryMaskedEdit.MyCustomProperty;

                if (prop.Equals(txtPhone.Text))
                { rPhone.Checked = true; }
                else
                if (prop.Equals(txtSSN.Text))
                { rSSN.Checked = true; }
                else
                if (prop.Equals(txtZip.Text))
                { rZip.Checked = true; }
                else
                if (prop.Equals(txtZip4.Text))
                { rZip4.Checked = true; }
                else
                {
                    rCustom.Checked = true;
                    txtCustom.Text = prop;
                }
            }
        }

        //save the value for the mask from the ascx to the field property
        //this is the value the plugin will use to format the masked input
        public void OnSaveChange(SPField field, bool bNewField)
        {
            jQueryMaskedEdit jme = (jQueryMaskedEdit)field;

            jme.IsNew = bNewField;

            if (rPhone.Checked)
            { jme.MyCustomProperty = txtPhone.Text; }
            else
            if (rSSN.Checked)
            { jme.MyCustomProperty = txtSSN.Text; }
            else
            if (rZip.Checked)
            { jme.MyCustomProperty = txtZip.Text; }
            else
            if (rZip4.Checked)
            { jme.MyCustomProperty = txtZip4.Text; }
            else
            { jme.MyCustomProperty = txtCustom.Text.Trim(); }
        }


        // Properties
        public bool DisplayAsNewSection
        {
            get
            {
                return false;
            }
        }
    }
}

This is the field class. Here I' inheriting form the SPFieldText since it most closely matches what we're trying to produce. This class provides the plumbing for the field. It saves the settings from the user control above, saves the values that users type in, validates the input, provides default display rendering. Luckily, most of this functionality is built into the SPFieldText class or provided by the Visual Studio project.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI;
using System.Web.UI.WebControls;


namespace MaskedEditField
{
    public class jQueryMaskedEdit : SPFieldText
    {
        private static string[] CustomPropertyNames = new string[] { "MyCustomProperty" };

        public jQueryMaskedEdit(SPFieldCollection fields, string fieldName)
            : base(fields, fieldName)
        {
            InitProperties();
        }

        public jQueryMaskedEdit(SPFieldCollection fields, string typeName, string displayName)
            : base(fields, typeName, displayName)
        {
            InitProperties();
        }

        #region Property storage and bug workarounds - do not edit

        /// <summary>
        /// Indicates that the field is being created rather than edited. This is necessary to
        /// work around some bugs in field creation.
        /// </summary>
        public bool IsNew
        {
            get { return _IsNew; }
            set { _IsNew = value; }
        }
        private bool _IsNew = false;

        /// <summary>
        /// Backing fields for custom properties. Using a dictionary to make it easier to abstract
        /// details of working around SharePoint bugs.
        /// </summary>
        private Dictionary<string, string> CustomProperties = new Dictionary<string, string>();

        /// <summary>
        /// Static store to transfer custom properties between instances. This is needed to allow
        /// correct saving of custom properties when a field is created - the custom property
        /// implementation is not used by any out of box SharePoint features so is really buggy.
        /// </summary>
        private static Dictionary<string, string> CustomPropertiesForNewFields = new Dictionary<string, string>();

        /// <summary>
        /// Initialise backing fields from base property store
        /// </summary>
        private void InitProperties()
        {
            foreach (string propertyName in CustomPropertyNames)
            {
                CustomProperties[propertyName] = base.GetCustomProperty(propertyName) + "";
            }
        }

        /// <summary>
        /// Take properties from either the backing fields or the static store and
        /// put them in the base property store
        /// </summary>
        private void SaveProperties()
        {
            foreach (string propertyName in CustomPropertyNames)
            {
                base.SetCustomProperty(propertyName, GetCustomProperty(propertyName));
            }
        }

        /// <summary>
        /// Get an identifier for the field being added/edited that will be unique even if
        /// another user is editing a property of the same name.
        /// </summary>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        private string GetCacheKey(string propertyName)
        {
            return SPContext.Current.GetHashCode() + "_" + (ParentList == null ? "SITE" : ParentList.ID.ToString()) + "_" + propertyName;
        }

        /// <summary>
        /// Replace the buggy base implementation of SetCustomProperty
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="propertyValue"></param>
        new public void SetCustomProperty(string propertyName, object propertyValue)
        {
            if (IsNew)
            {
                // field is being added - need to put property in cache
                CustomPropertiesForNewFields[GetCacheKey(propertyName)] = propertyValue + "";
            }

            CustomProperties[propertyName] = propertyValue + "";
        }

        /// <summary>
        /// Replace the buggy base implementation of GetCustomProperty
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="propertyValue"></param>
        new public object GetCustomProperty(string propertyName)
        {
            if (!IsNew && CustomPropertiesForNewFields.ContainsKey(GetCacheKey(propertyName)))
            {
                string s = CustomPropertiesForNewFields[GetCacheKey(propertyName)];
                CustomPropertiesForNewFields.Remove(GetCacheKey(propertyName));
                CustomProperties[propertyName] = s;
                return s;
            }
            else
            {
                return CustomProperties[propertyName];
            }
        }

        /// <summary>
        /// Called when a field is created. Without this, update is not called and custom properties
        /// are not saved.
        /// </summary>
        /// <param name="op"></param>
        public override void OnAdded(SPAddFieldOptions op)
        {
            base.OnAdded(op);
            Update();
        }

        #endregion


        public override BaseFieldControl FieldRenderingControl
        {
            get
            {
                BaseFieldControl fieldControl = new jQueryMaskedEditControl(this);
                fieldControl.FieldName = InternalName;
                return fieldControl;
            }
        }


        public override void Update()
        {
            SaveProperties();
            base.Update();
        }


        public override string GetValidatedString(object value)
        {
            if ((this.Required == true) && (value == null))
            { throw new SPFieldValidationException("This is a required field."); }

            return base.GetValidatedString(value);
        }


        public override object GetFieldValue(string value)
        {
            if (String.IsNullOrEmpty(value))
                return null;

            return value;
        }

        public string MyCustomProperty
        {
            get { return this.GetCustomProperty("MyCustomProperty") + ""; }
            set { this.SetCustomProperty("MyCustomProperty", value); }
        }
    }

}



This is the class that renders the control users interact with in the new and edit forms. It closely resembles a webpart. Here is where the JavaScript is written to the page.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;


namespace MaskedEditField
{
    public class jQueryMaskedEditControl : BaseFieldControl
    {
        private jQueryMaskedEdit field;
        private TextBox txtBox;
        private string txtVal;
      
        // Used for the linked script file
        private const string MaskedInputJS = "jquery.maskedinput-1.2.2.min.js";
        private const string MaskedInputScriptKey = "jQueryMaskedInputKey";
        private const string IncludeScriptFormat =
        @"<script type=""{0}"" src=""{1}""></script>";
      

        public jQueryMaskedEditControl(jQueryMaskedEdit parentField)
        {
            this.field = parentField;
            this.txtBox = new TextBox();
        }

        //get/set value for custom field
        public override object Value
        {
            get
            {
                return txtBox.Text;
            }
            set
            {
                txtBox.Text = string.Empty;
                string txtVal = value as String;
                if (txtVal != null)
                {
                    txtBox.Text = txtVal;
                }
            }
        }


        protected override void CreateChildControls()
        {
            if (this.Field == null
                || this.ControlMode == SPControlMode.Display
                || this.ControlMode == SPControlMode.Invalid)
                return;
          
            base.CreateChildControls();

            txtBox = new TextBox();
            this.Controls.Add(txtBox);
        }

        //Update field value with user input & check field validation
        public override void UpdateFieldValueInItem()
        {
            this.EnsureChildControls();
            try
            {
                this.Value = this.txtBox.Text;
                this.ItemFieldValue = this.Value;
            }

            catch (Exception ex)
            {
                this.IsValid = false;
                this.ErrorMessage = "* " + ex.Message;
            }
        }


        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (this.ControlMode == SPControlMode.Edit ||
                this.ControlMode == SPControlMode.New)
            { RegisterCommonScript(); }
        }


        //Function which will register the linked file script and the embedded script
        protected void RegisterCommonScript()
        {
            string location = null;
         
            //include the mask plugin on the page
            if (!Page.ClientScript.IsClientScriptBlockRegistered(MaskedInputScriptKey))
            {
                location = @"/_layouts/";
                string includeScript =
                String.Format(IncludeScriptFormat, "text/javascript", location + MaskedInputJS);
                Page.ClientScript.RegisterClientScriptBlock(typeof(jQueryMaskedEditControl), MaskedInputScriptKey, includeScript);
            }

            if (!Page.ClientScript.IsClientScriptBlockRegistered(txtBox.ClientID))
            {
                //this is where the mask plugin is hooked into our field, its all happening on the clients machine
                string function =
                     "<script type=\"text/javascript\">jQuery(function($){$(\"input[id*=" + txtBox.ClientID +
                     "]\").mask(\"" + field.MyCustomProperty + "\"); });</script>";
                Page.ClientScript.RegisterClientScriptBlock(typeof(jQueryMaskedEditControl), txtBox.ClientID, function);
            }
        }

    }
}


XML used to define the field.
<?xml version="1.0" encoding="utf-8" ?>
<FieldTypes>
  <FieldType>
    <Field Name="TypeName">jQueryMaskedEdit</Field>
    <Field Name="ParentType">Text</Field>
    <Field Name="TypeDisplayName">jQueryMaskedEdit</Field>
    <Field Name="TypeShortDescription">jQuery Masked Edit</Field>
    <Field Name="UserCreatable">TRUE</Field>
    <Field Name="Sortable">TRUE</Field>
    <Field Name="AllowBaseTypeRendering">TRUE</Field>
    <Field Name="Filterable">TRUE</Field>
    <Field Name="FieldTypeClass">MaskedEditField.jQueryMaskedEdit, MaskedEditField, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ab6ae01ba130938e</Field>
    <Field Name="FieldEditorUserControl">/_controltemplates/jQueryMaskedEditFieldEditor.ascx</Field>
    <PropertySchema>
      <Fields>
        <Field Hidden="TRUE" Name="MyCustomProperty"
        DisplayName="My Custom Property"
        Type="Text">
        </Field>
      </Fields>
      <Fields></Fields>
    </PropertySchema>
    <RenderPattern Name="DisplayPattern">
        <Column />
    </RenderPattern>
  </FieldType>
</FieldTypes>


Display View





Edit View

Thursday, November 12, 2009

SharePoint Lightbox

I've been using light boxes within SharePoint for a couple of years now. If you've seen SharePoint 2010 Microsoft has decided to use them too. The older light boxes greybox and thickbox work well but aren't as attractive as newer versions and are no longer being supported. So, it was time to find a new script to use. First, I tried colorbox. Worked just fine in FF but not in IE. I then tried jQuery UI Dialog. I really wanted to use it for a couple of reasons. jQuery UI  has a number of other effects that are really nice and it looks like the product will be supported for the foreseeable future. I was able to get it to load pages in an iframe but it wasn't ideal. I'm going to revisit this one in the future. Then I found a product called SimpleModal . It is simple and modal and works in iframe so AJAX and postbacks work well.

Download Simple Modal and the x.png image 
SimpleModal

Upload the script and image to a document library

Create a new Webpart Page

Add a CEWP (Content Editor) Webpart to the page and add the following script 

<style type="text/css">
#simplemodal-container a.modalCloseImg {
    //This is the image you uploaded
    background:url('javascript/x.png') no-repeat;
    width:25px;
    height:29px;
    display:inline;
    z-index:3200;
    position:absolute;
    top:-15px;
    right:-18px;
    cursor:pointer;
}
#simplemodal-overlay {background-color:#000;}
#simplemodal-container {background-color:#333; border:8px solid #444; padding:12px;}
</style>

//This is the jQuery library see jQuery Cross Site Lists for an explanation
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>

//This is the Simple Modal script you uploaded into a document library 
<script type="text/javascript" src="javascript/jquery.simplemodal-1.3.3.min.js"></script>


Add another CEWP Webpart to the page (under the first one) and add the following script
<script>
   function modalMe(){
       //You need to enter your own page here
       var src = "http://test/sites/test/LightBoxDemo/Lists/LightTasks/NewForm.aspx";
       $.modal('<iframe src="' + src + '" height="450" width="830" style="border:0">');
   }
</script>

<input type="button" onclick="modalMe();" value="Open Modal Window" ></input>

Click the button and you should get something like this. If something goes wrong and you can't close the window just refresh the browser.

Thursday, November 5, 2009

A Colorful SharePoint Calendar

A calendar that has events with color coded is easier to read and more attractive. This calendar is made possible with a jQuery, a calculated column, and a customized view. First, lets look at the results. The month, week and day views are all modified and available for use.











Step 1 - Create A Color Column
Create a new column called color, make it a choice field and add a number of colors for your users to select.






Step 2 - Create A Calculated Column
Enter the following in the formula field

="<span style='background-color:"&color&";padding:4px;text-align:center;width:100%;'>"&Title&"</span>"




Step 3 - Create View that displays the calculated field




Step 4 - Reference the jQuery Library 
Add a CEWP to the page and add the following line
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>


Step 5 - Add the script
Add a new CEWP to the page and add the following script
<script type="text/javascript">
// Make sure the page has been loaded
$(document).ready(function() {

  $(".ms-cal-dayitem, .ms-cal-dayMultiDay, .ms-cal-monthitem, .ms-cal-defaultbgcolor").each(function(){
      var bHtml = $(this).html();
      bHtml = bHtml.replace(/&lt;(.+?)&gt;/g,'<$1>');   
      $(this).html(bHtml);
      $(this).find('span').each(function(){
         var tdstyle = $(this).attr('style');
         $(this).removeAttr('style');
         $(this).closest('a').attr('style','color:black').removeAttr('class');
 $(this).closest('td').attr('style',tdstyle).removeAttr('class').removeAttr("onmouseover").removeAttr("onmouseout");
      });
   });

});
</script>

Finally - Add the Calendar View

Sunday, October 18, 2009

SharePoint Cross Site List Views with jQuery

Displaying or sharing lists across webs or site collections is not supported out of the box by SharePoint. The best way to overcome this limitation is by loading the list dynamically. Ok, sounds great but how am I supposed to do that? No worries, it's really pretty easy. I will use jQuery's load function to do the bulk of the work. The code here has been adapted from Christophe's solution . There are a number of changes, the most important being enabling sorting by column.

Load the jQuery library
I'm adding a content editor web part to include the jQuery library. You should probably add the library to your master page. Once you start using it you'll want to add it all over the place. Add a CEWP to your page and add the following script tag. This will load jQuery from Google.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>

Add the Script to the page
Add a new CEWP, yes you have to, to your page and add the following script.

<DIV id="ListPlaceholder"><span><b>...Loading...</b></span></DIV>

<script type="text/javascript">
// Make sure the page has been loaded
$(document).ready(function() {

    // Paste the URL of the source list below:
    var SelectedView = "http://test/sites/test/Lists/Tasks/AllItems.aspx";

    // Paste the URL of Add new items page
    var AddNewItems = "http://test/sites/test/Lists/Tasks/NewForm.aspx" + "?Source="+location.href;

    // Title
    var mTitle = "My List Name";

    // Title tool tip
    var MouseOverHeader = "My List Name Description";

    var innerHTML = "<a href=" + AddNewItems + " tabindex='0' accesskey='W'><nobr><span>" + mTitle + "</span><span id='WebPartCaptionWPQ6'/></nobr></a>";

    // Load the list
    $("#ListPlaceholder").load(SelectedView+" [id^='WebPartWPQ'] .ms-listviewtable", function(){processView();setColumnClicks();});

    // Format the returned list view
    function processView(){

        //set the header of the content place holder
        var hdrNode = $("#ListPlaceholder").closest('tr').prev().find('h3');
        hdrNode.html(innerHTML);
        hdrNode.parent(0).attr('title', MouseOverHeader);

        //Remove the dropdown menus for items
        $("#ListPlaceholder *").removeAttr("id").removeAttr("onclick").removeAttr("onfocus").removeAttr("onmouseover");
    
//Format the Items
        $("#ListPlaceholder a").each(function() {
            if ($(this).attr("href").indexOf("javascript") == -1){
                if ($(this).attr("href").indexOf("mailto") == -1){
                    if ($(this).attr("href").indexOf("?") > 0){
                       $(this).attr("href",$(this).attr("href")+"\&Source="+location.href);}
                    else {$(this).attr("href", $(this).attr("href")+"?Source="+location.href);}
                }
            }
        });// close each
    }// close processView

//Make the titles sortable
    function setColumnClicks(){
        $("#ListPlaceholder a[sortingfields]").click(function(){
            var srtFields = $(this).attr('sortingfields');
            $("#ListPlaceholder").load(SelectedView +"?" + srtFields + "  [id^='WebPartWPQ'] .ms-listviewtable", function(){processView();setColumnClicks();});
        });   
    }

});//Close document.ready
</script>

Explaining the script
This line in the script really does all of the work

$("#ListPlaceholder").load(SelectedView+" [id^='WebPartWPQ'] .ms-listviewtable", function(){processView();setColumnClicks();});

This line means the following. Find the element on this page with an id of ListPlaceholder and load the content from SelectedView. Only load the content where the element id starts with WebPartWPQ and has the class ms-listviewtable. After loading the content run functions processView() and setColumnClicks().
Another question you may have is what is this Source used all over the place for ? SharePoint uses Source= in the url to return to the previous page.