Monthly Archives: October 2016

Fetch and save Sitecore Item in SPEAK 2 using Sitecore.Services.Client

testapppage

Hello guys, yet another post about SPEAK 🙂
In this medium small post I would like to share with you how I work with Sitecore items in SPEAK.

Let’s say you need to retrieve a Sitecore item, modify it and update it.
In this example the Sitecore item is exposed in an url, like this:

https://somehost.com/sitecore/client/MyApp/Applications/TestApp/TestAppPage?id=7E4CA53A-59AA-4C08-9EEF-71DDF66E8A8E&database=master&language=en&version=1

This will be a simple page containing two text boxes and a save button(See above)
Here is the Design Layout for the page:
tasklayout
I really like the Grid rendering and guess what it’s responsive friendly 🙂

To get an item I do something like this:

GetTestData: function (callback) {

	var self = this;
	var testObject = {};
	testObject["id"] = Sitecore.Speak.utils.url.parameterByName("id");
	testObject["language"] = Sitecore.Speak.utils.url.parameterByName("language");
	testObject["version"] = Sitecore.Speak.utils.url.parameterByName("version");
	testObject["database"] = Sitecore.Speak.utils.url.parameterByName("database");
	testObject["databaseUri"] = new _sc.Definitions.Data.DatabaseUri(testObject["database"]);
	testObject["itemUri"] = new _sc.Definitions.Data
		.ItemUri(testObject["databaseUri"], testObject["id"]);
	testObject["itemVersionUri"] = new _sc.Definitions.Data
		.ItemVersionUri(testObject["itemUri"],
			testObject["language"],
			parseInt(testObject["version"]));

	var db = new _sc.Definitions.Data.Database(testObject["databaseUri"]);

	db.getItem(testObject["itemVersionUri"],
		function (item) {

			testObject["itemName"] = item.itemName;
			testObject["itemDisplayname"] = item.$displayName;

			var testHeaderField = item.getFieldById(self.Constants.TestHeaderFieldId);
			testObject["itemFieldTestHeader"] = testHeaderField.value;

			var testDescriptionField = item.getFieldById(self.Constants.TestDescriptionFieldId);
			testObject["itemFieldTestDescription"] = testDescriptionField.value;

			callback(testObject);

		});


}

The reason for the callback function is because of db.getItem. When the method has retrieved the Sitecore item I’ll put the data in a JSON object.
The Sitecore.Speak.utils.url.parameterByName is indeed handy 🙂
(I’m not using Sitecore.Services.Client here)

To call the GetTestData method:

BindTestData: function () {
	var self = this;

	self.GetTestData(function (testObject) {
		self.TestData = testObject;

		//Do some binding stuff to the controls on your Speak layout page
		self.MyHeaderTextBox.Value = self.TestData.itemFieldTestHeader;
		self.MyDescriptionTextBox.Value = self.TestData.itemFieldTestDescription;
	});

}

Here we will bind/set the data to the text boxes.
(self.TestData is just a container)

To update a Sitecore item we will use Sitecore.Services.Client. Thanks to Kevin Obee’s great “living” documentation, http://docs.itemserviceapi.apiary.io, he made it so much easier to understand how it works.
There is also a great post that describes the differences between Sitecore Item Web API and Sitecore.Services.Client – What is Sitecore.Services.Client?

Here is how the we update the Sitecore item using Sitecore.Services.Client

UpdateSitecoreData: function (callback) {
	var self = this;

	var request = new XMLHttpRequest();

	var url = self.StringFormat("/sitecore/api/ssc/item/{0}?database={1}&language={2}&version={3}",
		self.TestData.id,
		self.TestData.database,
		self.TestData.language,
		self.TestData.version);

	request.open("PATCH", url);

	request.setRequestHeader('Content-Type', 'application/json');

	var body = {
		'TestHeader': self.TestData.itemFieldTestHeader,
		'TestDescription': self.TestData.itemFieldTestDescription
	};

	request.send(JSON.stringify(body));

	request.onreadystatechange = function () {

		return callback(this.readyState, this.status, this.responseText);

	};
}

Again I’m using a callback, since we need to wait for the response if the save was successful or not. TestHeader and TestDescription are the field names of the Sitecore item.

Another thing, if you would get the forbidden page (You do not have permission to view this directory or page) when using the Sitecore.Services.Client. That means you probably have to take a look at the security policy settings in Sitecore.Services.Client.config. Find the setting that works best for you.

And here we call UpdateSitecoreData:

SaveTestData: function () {
	var self = this;

	//Get the data from the text boxes 
	self.TestData.itemFieldTestHeader = self.MyHeaderTextBox.Value;
	self.TestData.itemFieldTestDescription = self.MyDescriptionTextBox.Value;
	
	self.UpdateSitecoreData(function (readyState, status, responseText) {
		//Failure
		if (readyState === 4 && (this.status === 404 || status === 500)) {
			self.StatusAlertConfirmationDialog["HeaderText"] = "Save - error";
			self.StatusAlertConfirmationDialog["Title"] = "An error occurred while saving, please contact support";
			self.StatusAlertConfirmationDialog["IconType"] = "error";
			self.StatusAlertConfirmationDialog["Message"] = responseText;
			self.StatusAlertConfirmationDialog.show();
			self.StatusAlertConfirmationDialog.IsVisible = true;

		}

		//Success
		if (readyState === 4 && this.status !== 404 && status !== 500) {
			self.BindTestData();
		}


	});

}

If you succeed with the update we will “rebind” the data by calling BindTestData, if not we will show a StatusAlertConfirmationDialog(ConfirmationDialog).

Here is the full PageCode file:

(function (Speak) {

	Speak.pageCode({
		initialized: function () {
			console.log("Start TestAppPage");
			var self = this;

			self.SaveTestButton.on("click", self.ConfirmSave, this);

			self.UpdateTestDataConfirmationDialog.on("close",
				function (data) {

					if (data === "ok") {

						self.SaveSearch();
				
					}
                    self.UpdateTestDataConfirmationDialog.IsVisible = false;
				},
				this);	

			self.BindTestData();


		},
		ConfirmSave: function () {
			var self = this;
			self.UpdateTestDataConfirmationDialog.show();
			self.UpdateTestDataConfirmationDialog.IsVisible = true;
		},
		BindTestData: function () {
			var self = this;

			self.GetTestData(function (testObject) {
				self.TestData = testObject;

				//Do some binding stuff to the controls on your Speak layot page
                self.MyHeaderTextBox.Value = self.TestData.itemFieldTestHeader;
                self.MyDescriptionTextBox.Value = self.TestData.itemFieldTestDescription;
			});


		},
		GetTestData: function (callback) {

			var self = this;

			var testObject = {};
			testObject["id"] = Sitecore.Speak.utils.url.parameterByName("id");
			testObject["language"] = Sitecore.Speak.utils.url.parameterByName("language");
			testObject["version"] = Sitecore.Speak.utils.url.parameterByName("version");
			testObject["database"] = Sitecore.Speak.utils.url.parameterByName("database");
			testObject["databaseUri"] = new _sc.Definitions.Data.DatabaseUri(testObject["database"]);
			testObject["itemUri"] = new _sc.Definitions.Data
				.ItemUri(testObject["databaseUri"], testObject["id"]);
			testObject["itemVersionUri"] = new _sc.Definitions.Data
				.ItemVersionUri(testObject["itemUri"],
					testObject["language"],
					parseInt(testObject["version"]));

			var db = new _sc.Definitions.Data.Database(testObject["databaseUri"]);

			db.getItem(testObject["itemVersionUri"],
				function (item) {

					testObject["itemName"] = item.itemName;
					testObject["itemDisplayname"] = item.$displayName;

					var testHeaderField = item.getFieldById(self.Constants.TestHeaderFieldId);
					testObject["itemFieldTestHeader"] = testHeaderField.value;

					var testDescriptionField = item.getFieldById(self.Constants.TestDescriptionFieldId);
					testObject["itemFieldTestDescription"] = testDescriptionField.value;

					callback(testObject);

				});


		},
		Constants: {
			"TestHeaderFieldId": "{3699A67C-A4BC-486D-9A37-9E4554AE46CA}",
			"TestDescriptionFieldId": "{E85C0CA0-75F2-4FB7-B2B5-03D8B97EBCCA}"
		},
		TestData: function () {
			var content;

			return content;
		},
		SaveTestData: function () {
			var self = this;

			//Get the data from the textboxes 
			self.TestData.itemFieldTestHeader = self.MyHeaderTextBox.Value;
			self.TestData.itemFieldTestDescription = self.MyDescriptionTextBox.Value;
			
			self.UpdateSitecoreData(function (readyState, status, responseText) {
				//Failure
				if (readyState === 4 && (this.status === 404 || status === 500)) {
					self.StatusAlertConfirmationDialog["HeaderText"] = "Save - error";
					self.StatusAlertConfirmationDialog["Title"] = "An error occurred while saving, please contact support";
					self.StatusAlertConfirmationDialog["IconType"] = "error";
					self.StatusAlertConfirmationDialog["Message"] = responseText;
					self.StatusAlertConfirmationDialog.show();
					self.StatusAlertConfirmationDialog.IsVisible = true;

				}

				//Success
				if (readyState === 4 && this.status !== 404 && status !== 500) {
					self.BindTestData();
				}


			});


		},
		UpdateSitecoreData: function (callback) {
			var self = this;

			var request = new XMLHttpRequest();

			var url = self.StringFormat("/sitecore/api/ssc/item/{0}?database={1}&language={2}&version={3}",
				self.TestData.id,
				self.TestData.database,
				self.TestData.language,
				self.TestData.version);

			request.open("PATCH", url);

			request.setRequestHeader('Content-Type', 'application/json');

			var body = {
				'TestHeader': self.TestData.itemFieldTestHeader,
				'TestDescription': self.TestData.itemFieldTestDescription
			};

			request.send(JSON.stringify(body));

			request.onreadystatechange = function () {

				return callback(this.readyState, this.status, this.responseText);

			};
		},
		StringFormat: function () {
			var s = arguments[0];
			for (var i = 0; i < arguments.length - 1; i++) {
				var reg = new RegExp("\\{" + i + "\\}", "gm");
				s = s.replace(reg, arguments[i + 1]);
			}
			return s;
		}
		
	});
})(Sitecore.Speak);

If you noticed there is a call to ConfirmSave when the Save button is clicked which will popup a ConfirmationDialog.

ConfirmSave: function () {
	var self = this;
	self.UpdateTestDataConfirmationDialog.show();
	self.UpdateTestDataConfirmationDialog.IsVisible = true;
}

If you are wondering about the self.UpdateTestDataConfirmationDialog.IsVisible = true, yes the show() method is enough to pop up the dialog. But I need to know that the dialog is visible, that’s why I’m using the IsVisible attribute.

Finally in the initialized function we are listening to the UpdateTestDataConfirmationDialog’s close event. If OK button is clicked we will call the SaveSearch.

self.UpdateTestDataConfirmationDialog.on("close",
function (data) {

	if (data === "ok") {

		self.SaveSearch();
 
	}
	self.UpdateTestDataConfirmationDialog.IsVisible = false;
},
this);

That’s it and keep doing some good SPEAK stuff out there.

That’s all for now folks 🙂

Advertisements

Put JSON data in your SearchDataSource and bind it to SearchableDroplist – Sitecore SPEAK

droplist

Hello guys, I have been working with SPEAK for a while and some of the controls I’ve been using a lot are:
SearchDataSource and SearchableDropList(SPEAK 2 control)

The SearchableDropList is a droplist with search, the datasources you can use are:
QueryDataSource
SearchDataSource

If you have less than 100 items then QueryDataSource is OK(since it’s using Sitecore Query or Sitecore Fast Query) but if you have more, then use the SearchDataSource(using good old index).

An extremely cool thing regarding SearchDataSource is that you can add JSON data to it. Just create a JSON array and add it like this:

(function (Speak) {

	Speak.pageCode({


		initialized: function () {
			
			var self = this;
			
			self.BindJsonDataToMyCoolSearchDataSource();


		},
        BindJsonDataToMyCoolSearchDataSource: function() {
			var self = this;

			var customJsonData = [];

			for (var i = 0; i < 10; i++) {

				var data = {};
				data["Value"] = i;
				data["Text"] = i + " some text"; 
				customJsonData.push();
			}


			self.MyCoolSearchDataSource.Items = customJsonData;

		}

	});
})(Sitecore.Speak);

To add the JSON data we just set the Items property on the SearchDataSource:
self.MyCoolSearchDataSource.Items = customJsonData;

Then in your SpeakLayout you will connect the SearchDataSource to your control, in this case the SearchableDropList:
speaksearch
To set the datasource for the SearchableDropList, we will use DynamicData. Here we point(bind) to the SearchDataSource:
{Binding MyCoolSearchDataSource.Items}

Next we will set DisplayFieldName to the JSON field “Text” and finally we set ValueFieldName to the JSON field “Value”.

Now you you will have a searchable droplist where you can search in your custom JSON data:
droplist

By the way the new version(Version 2) of Sitecore Rocks is so much faster and it has some very new nice features. It truly rocks 😉

That’s it and keep doing some good SPEAK stuff out there

That’s all for now folks 🙂

How to trigger an OK click on a Sitecore SPEAK ConfirmationDialog

dialog

This is a really quick post but I feel I have to share this with you guys.

I have been doing a lot of SPEAK lately and from time to time I end up struggling with something that should be really easy to do(and yes it is easy when you finally find the solution).

Anyways I had to trigger an OK click on a ConfirmationDialog(SPEAK 2 component) in SPEAK.
speak

In this case the ConfirmationDialog should be closed with the Enter key by trigger the OK button. Let’s take a look on the PageCode:

(function (Speak) {

	Speak.pageCode({


		initialized: function () {
			
			var self = this;
			
			self.MyConfirmationDialog.on("close",
				function (data) {

					if (data === "ok") {

						//Do some stuff
					}

				},
				this);
			

			jQuery(document).keypress(function (e) {

				if (e.which === 13) {

					if (self.MyConfirmationDialog.IsVisible) {
						self.MyConfirmationDialog.CloseClick = "ok";
						self.MyConfirmationDialog.hide();
					}

				}
			});


		}
	});
})(Sitecore.Speak);

To close the ConfirmationDialog we would call the hide method – self.MyConfirmationDialog.hide().

The trickier part was to tell what button should be clicked/triggered and that took a while for me to figure out.
In CloseClick you need to set what button you want to trigger, like this self.MyConfirmationDialog.CloseClick = “ok”

That’s it and keep doing some good SPEAK stuff out there

That’s all for now folks 🙂

Authorize your Web API controllers in Sitecore style

webapifilters01
I would like to share with you guys how easy it is to limit access to your Web API actions or controllers. Recently when I was working with a SPEAK component I had to make a Web API controller that only allowed users with a certain role(The Admin user will always have access).

So as always I looked at how Sitecore did and I just loved it 🙂

They are using Authentication filters.

Authentication filters let you set an authentication scheme for individual controllers or actions. That way, your app can support different authentication mechanisms for different HTTP resources.

To apply an authentication filter to a controller, decorate the controller class with the filter attribute.

What we need is a filter to authenticate if user is part of a specific role.
I took a glance at on one of the filters Sitecore did, AuthorizedReportingUserFilter.

using Sitecore.Globalization;
using Sitecore.Xdb.Configuration;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Sitecore.Cintel.Endpoint.Plumbing
{
  public class AuthorizedReportingUserFilter : AuthorizationFilterAttribute
  {
    public override void OnAuthorization(HttpActionContext actionContext)
    {
      if ((Context.User.IsAdministrator || Context.User.IsInRole("sitecore\\analytics reporting")) && (XdbSettings.Enabled && Context.User.IsAuthenticated))
        return;
      string message = Translate.Text("Unauthorized Access");
      actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, message);
      base.OnAuthorization(actionContext);
    }
  }
}

As you guys can see, in OnAuthorization that is where all the magic happens. In this case a check is made if user is part of analytics reporting and is authenticated.

It’s almost what we need. So what to do? Let’s make our own filter, like this:

using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Sitecore;
using Sitecore.Globalization;

namespace Sandbox.Website.Code.HttpFilters
{
  public class AuthorizedCustomRoleFilter : AuthorizationFilterAttribute
  {
    private readonly string _role;

    public AuthorizedCustomRoleFilter (string role)
    {
        _role = role;
    }

    public override void OnAuthorization(HttpActionContext actionContext)
    {
       if (Context.User.IsAdministrator || Context.User.IsInRole($"sitecore\\{_role}") && Context.User.IsAuthenticated)
       return;

       string message = Translate.Text("Unauthorized Access");
       actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, message);
       base.OnAuthorization(actionContext);
    }
  }
}

As you guys can see we have a parameter in the construct, that one allows us to add whatever role you want.

Here is an example where we use the new filter on a Web API controller.

namespace Sandbox.Website.Code.WebAPI.Controllers
{
  [AuthorizedCustomRoleFilter("Author")]
  public class MyController : ApiController
  {

So start doing a bunch of http filters for your web api controllers.

That’s all for now folks 🙂