How to implement moving of items up and down?
Hi there,
There is an ability to change the ordering of pages from Sitefintiy backend, i.e. move them up or down.
I'd like to implement the same functionality for my custom module.
I've investigated the Products sample and changed the following method in ProductsDefinitions.cs:
public
static
void
FillActionMenuItems(ConfigElementList<WidgetElement> menuItems, ConfigElement parent,
string
resourceClassId)
menuItems.Add(
CreateActionMenuCommand(menuItems,
"View"
, HtmlTextWriterTag.Li, PreviewCommandName,
"View"
, resourceClassId));
menuItems.Add(
CreateActionMenuCommand(menuItems,
"Delete"
, HtmlTextWriterTag.Li, DeleteCommandName,
"Delete"
, resourceClassId,
"sfDeleteItm"
));
menuItems.Add(
CreateActionMenuSeparator(menuItems,
"Separator"
, HtmlTextWriterTag.Li,
"sfSeparator"
,
"Edit"
, resourceClassId));
menuItems.Add(
CreateActionMenuCommand(menuItems,
"Content"
, HtmlTextWriterTag.Li, EditCommandName,
"Content"
, resourceClassId));
menuItems.Add(
CreateActionMenuSeparator(menuItems,
"Separator1"
, HtmlTextWriterTag.Li,
"sfSeparator"
,
"Move"
, resourceClassId));
menuItems.Add(
CreateActionMenuCommand(menuItems,
"Up"
, HtmlTextWriterTag.Li, MoveUpCommandName,
"MoveUp"
, resourceClassId,
"sfMoveUp"
));
menuItems.Add(
CreateActionMenuCommand(menuItems,
"Down"
, HtmlTextWriterTag.Li, MoveDownCommandName,
"MoveDown"
, resourceClassId,
"sfMoveDown"
));
Hi Anton,
Right, the command names for the Move up and down are respectively, moveUp and moveDown.
In order to achieve this effect, you should introduce also a JavaScript extension that handles these commands and then requests the service through the binder. Keep in mind that in order to make your extension script work you should attach it through the definitions of the module in a similar fashion:
var externalScripts =
new
Dictionary<
string
,
string
>();
externalScripts.Add(
"ProductCatalogSample.Web.UI.Public.ProductsMasterListViewExtensions.js, ProductCatalogSample"
,
"OnMasterViewLoaded"
);
productsGridView.ExternalClientScripts = externalScripts;
// called by the MasterGridView when it is loaded
function
OnMasterViewLoaded(sender, args)
var
masterView = sender;
var
extender =
new
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension(masterView);
extender.initialize();
Type.registerNamespace(
"Telerik.Sitefinity.Modules.Pages.Web.UI"
);
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension =
function
(masterView)
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension.initializeBase(
this
);
// Main components
this
._masterView = masterView;
this
._binder =
null
;
this
._itemsGrid = ;
this
._itemsList = ;
this
._itemsTreeTable = ;
this
._masterCommandDelegate =
null
;
this
._itemCommandDelegate =
null
;
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension.SelectionType = None: 0, Single: 1, Multiple: 2 ;
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension.prototype =
initialize:
function
()
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension.callBaseMethod(
this
,
'initialize'
);
this
._masterCommandDelegate = Function.createDelegate(
this
,
this
._masterCommandHandler);
this
._itemCommandDelegate = Function.createDelegate(
this
,
this
._itemCommandHandler);
,
dispose:
function
()
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension.callBaseMethod(
this
,
'dispose'
);
delete
this
._masterCommandDelegate;
delete
this
._itemCommandDelegate;
,
_itemCommandHandler:
function
(sender, args)
var
page = args.get_commandArgument();
var
pageId = args.get_commandArgument().Id;
var
currentList =
this
._masterView.get_currentItemsList();
var
binder = currentList.getBinder();
switch
(args.get_commandName())
case
'moveUp'
:
this
._movePage(pageId, binder,
"up"
);
break
;
case
'moveDown'
:
this
._movePage(pageId, binder,
"down"
);
break
;
,
_movePage:
function
(pageId, binder, direction)
if
(binder.canMoveNode(pageId, direction))
binder.moveNode(pageId, direction);
else
this
._showCantMoveMessage(direction);
,
Hi George,
Thank you for help!
It became a bit clearer for me. I tried to implement your code and finally I got a JavaScript error:
Error: binder.canMoveNode is not a function
Is seems, some JavaScript code is absent. Can you please tell me which resource I also need to include?
Best regards,
Anton.
Hello Anton Mernov,
This is because the binder of the pages (RadTreeBinder) is different from the one in the grid content listings, so you should implement your own logic for sorting these items on the server.
Here's the service handler in PagesService.cs,
private
void
BatchMovePageInternal(
string
[] sourcePageIds,
string
providerName,
string
direction)
ServiceUtility.RequestAuthentication();
var pagesIds =
new
List<Guid>(sourcePageIds.Length);
foreach
(var pageId
in
sourcePageIds)
pagesIds.Add(
new
Guid(pageId));
if
(pagesIds.Count > 1)
throw
new
NotSupportedException(
"The batch move operation is not supported"
);
var page = App.WorkWith().Page(pagesIds[0]);
if
(direction ==
"up"
)
page.Move(Move.Up, 1);
else
page.Move(Move.Down, 1);
page.SaveChanges();
SiteMapBase.Cache.Flush();
public
override
void
MovePageNode(PageNode nodeToMove, Move move,
int
numberOfPlaces)
if
(numberOfPlaces < 1)
throw
new
ArgumentException(
"numberOfPlaces argument must be larger than 0."
);
var parentId = nodeToMove.Parent.Id;
var ordinal = nodeToMove.Ordinal;
switch
(move)
case
Telerik.Sitefinity.Modules.Pages.Move.Up:
var targetOrdinalsUp =
this
.GetPageNodes()
.Where(pt => pt.Parent !=
null
&& pt.Parent.Id == parentId && pt.Ordinal < ordinal)
.OrderByDescending(pt => pt.Ordinal)
.Select(pt => pt.Ordinal)
.Skip(numberOfPlaces - 1)
.Take(2)
.ToList();
if
(targetOrdinalsUp.Count < 2)
this
.MovePageNode(nodeToMove, MoveTo.FirstInTheCurrentLevel);
else
nodeToMove.SetOrdinalBetween(targetOrdinalsUp[1],targetOrdinalsUp[0]);
break
;
case
Telerik.Sitefinity.Modules.Pages.Move.Down:
var targetOrdinalsDown =
this
.GetPageNodes()
.Where(pt => pt.Parent !=
null
&& pt.Parent.Id == parentId && pt.Ordinal > ordinal)
.OrderBy(pt => pt.Ordinal)
.Select(pt => pt.Ordinal)
.Skip(numberOfPlaces - 1)
.Take(2)
.ToList();
if
(targetOrdinalsDown.Count < 2)
this
.MovePageNode(nodeToMove, MoveTo.LastInTheCurrentLevel);
else
nodeToMove.SetOrdinalBetween(targetOrdinalsDown[0], targetOrdinalsDown[1]);
break
;
default
:
throw
new
NotSupportedException();
/// <summary>
/// Moves the page node passed as first argument by the specified number of places, in the direction given by the
/// <see cref="Move"/> enumeration.
/// </summary>
/// <param name="nodeToMove">The node to move.</param>
/// <param name="move">A value representing the direction in which the node will be moved.</param>
/// <param name="numberOfPlaces">The number of places to move.</param>
public
void
MovePageNode(PageNode nodeToMove, Move move,
int
numberOfPlaces)
this
.Provider.MovePageNode(nodeToMove, move, numberOfPlaces);
this
.ApplyActionToLanguageRelatedNodes(nodeToMove,
pn =>
this
.Provider.MovePageNode(pn, move, numberOfPlaces);
);
namespace
Telerik.Sitefinity.Model
/// <summary>
/// Represents Ordered Item interface.
/// </summary>
public
interface
IOrderedItem
/// <summary>
/// Gets or sets the ordinal number of the item.
/// </summary>
/// <value>The ordinal number.</value>
float
Ordinal
get
;
set
;
I'm trying to implement similar move up/down functionality.
So far I registered external script. Per example in the second post I came up with the following:
// called by the MasterGridView when it is loaded
function
OnMasterViewLoaded(sender, args)
var
masterView = sender;
var
extender =
new
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension(masterView);
extender.initialize();
Type.registerNamespace(
"Telerik.Sitefinity.Modules.Pages.Web.UI"
);
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension =
function
(masterView)
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension.initializeBase(
this
);
// Main components
this
._masterView = masterView;
this
._binder =
null
;
this
._itemsGrid = ;
this
._itemsList = ;
this
._itemsTreeTable = ;
this
._masterCommandDelegate =
null
;
this
._itemCommandDelegate =
null
;
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension.SelectionType = None: 0, Single: 1, Multiple: 2 ;
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension.prototype =
initialize:
function
()
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension.callBaseMethod(
this
,
'initialize'
);
this
._masterCommandDelegate = Function.createDelegate(
this
,
this
._masterCommandHandler);
this
._itemCommandDelegate = Function.createDelegate(
this
,
this
._itemCommandHandler);
,
dispose:
function
()
Telerik.Sitefinity.Modules.Pages.Web.UI.PagesMasterGridViewExtension.callBaseMethod(
this
,
'dispose'
);
delete
this
._masterCommandDelegate;
delete
this
._itemCommandDelegate;
,
_itemCommandHandler:
function
(sender, args)
var
page = args.get_commandArgument();
var
pageId = args.get_commandArgument().Id;
var
currentList =
this
._masterView.get_currentItemsList();
var
binder = currentList.getBinder();
switch
(args.get_commandName())
case
'moveUp'
:
this
._movePage(pageId, binder,
"up"
);
break
;
case
'moveDown'
:
this
._movePage(pageId, binder,
"down"
);
break
;
,
_movePage:
function
(pageId, binder, direction)
if
(binder.canMoveNode(pageId, direction))
binder.moveNode(pageId, direction);
else
this
._showCantMoveMessage(direction);
callBaseMethod ->_getBaseMethod
:
window.Type = Function; Type.prototype.callBaseMethod =
function
(a, d, b)
var
c = Sys._getBaseMethod(
this
, a, d);
if
(!b)
return
c.apply(a);
else
return
c.apply(a, b)
Hello Denis,
It seems that your control is not correctly getting the base scripts. Can you please show us the GetScriptReferences and GetScriptDescriptors of your server side control?
All the best,
Radoslav Georgiev
the Telerik team
Hello Radoslav,
This is how I register scripts:
var portfolioItemsGridViewExternalScripts =
new
Dictionary<
string
,
string
>();
portfolioItemsGridViewExternalScripts.Add(
"PortfolioItemsModule.Web.UI.Public.PortfolioItemsListViewExtensions.js, PortfolioItemsModule"
,
"OnMasterViewLoaded"
);
// GridView element serves as the "List View" for the item list. Grid columns are defined later
var portfolioItemsGridView =
new
MasterGridViewElement(backendContentView.ViewsConfig)
ViewName = PortfolioItemsDefinitions.BackendListViewName,
ViewType =
typeof
(MasterGridView),
AllowPaging =
true
,
DisplayMode = FieldDisplayMode.Read,
ItemsPerPage = 50,
SearchFields =
"Title"
,
SortExpression =
"Title ASC"
,
Title =
"Portfolio Items"
,
ExternalClientScripts = portfolioItemsGridViewExternalScripts,
WebServiceBaseUrl =
"~/Sitefinity/Services/Content/PortfolioItems.svc/"
;
backendContentView.ViewsConfig.Add(portfolioItemsGridView);
CustomSettingsDesignerView : ContentViewDesignerViewclass.
Hi George,
Nice one
But it would be great if you provide us a downloadable sample of Product Module which supports Move Up and Move Down Buttons!
Thanks,
Saeed!
Hi,
I have implemented most of the logic. What is left is that you need to add the locig for changing the ordinal and saving the item. This should be done with a call to your web service. I have prepared sample based on the products module. I have implemented a property named Ordinal
1) To add the actions menu you need to do this in definitions. I guess you have something like bellow:
Copy Code
actionsColumn.MenuItems.Add(new CommandWidgetElement(actionsColumn.MenuItems)
Name = "Up",
WrapperTagKey = HtmlTextWriterTag.Li,
CommandName = "moveUp",
Text = "Up",
WidgetType = typeof(CommandWidget),
CssClass = "sfMoveUp",
);
actionsColumn.MenuItems.Add(new CommandWidgetElement(actionsColumn.MenuItems)
Name = "Down",
WrapperTagKey = HtmlTextWriterTag.Li,
Text = "Down",
CommandName = "moveDown",
WidgetType = typeof(CommandWidget),
CssClass = "sfMoveDown"
);
actionsColumn.MenuItems.Add(new CommandWidgetElement(actionsColumn.MenuItems)
Name = "Top",
WrapperTagKey = HtmlTextWriterTag.Li,
CommandName = "moveTop",
Text = "Top",
WidgetType = typeof(CommandWidget),
CssClass = "sfMoveUp",
);
actionsColumn.MenuItems.Add(new CommandWidgetElement(actionsColumn.MenuItems)
Name = "Bottom",
WrapperTagKey = HtmlTextWriterTag.Li,
Text = "Bottom",
CommandName = "moveBottom",
WidgetType = typeof(CommandWidget),
CssClass = "sfMoveDown"
);
var scripts =
new
Dictionary<
string
,
string
>();
scripts.Add(
string
.Format(
"0, 1"
,
ProductsDefinitions.ProductItemsMasterGridViewExtensionScript,
typeof
(ProductsModule).Assembly.FullName),
"OnMasterViewLoaded"
);
productsGridView.ExternalClientScripts = scripts;
private
static
readonly
string
ProductItemsMasterGridViewExtensionScript =
"ProductCatalogSample.Web.Controls.ProductItemsMasterGridViewExtensions.js"
;
// called by the MasterGridView when it is loaded
function
OnMasterViewLoaded(sender, args)
var
masterView = sender;
var
extender =
new
ProductCatalogSample.Web.Controls.ProductItemsMasterGridViewExtensions(masterView);
extender.initialize();
Type.registerNamespace(
"ProductCatalogSample.Web.Controls"
);
ProductCatalogSample.Web.Controls.ProductItemsMasterGridViewExtensions =
function
(masterView)
ProductCatalogSample.Web.Controls.ProductItemsMasterGridViewExtensions.initializeBase(
this
);
// Main components
this
._masterView = masterView;
this
._binder =
null
;
this
._itemMoveCommandDelegate =
null
;
ProductCatalogSample.Web.Controls.ProductItemsMasterGridViewExtensions.prototype =
initialize:
function
()
ProductCatalogSample.Web.Controls.ProductItemsMasterGridViewExtensions.callBaseMethod(
this
,
'initialize'
);
this
._itemMoveCommandDelegate = Function.createDelegate(
this
,
this
._itemMoveCommandHandler);
this
._masterView.get_itemsGrid().getBinder().add_onItemCommand(
this
._itemMoveCommandDelegate);
,
dispose:
function
()
ProductCatalogSample.Web.Controls.ProductItemsMasterGridViewExtensions.callBaseMethod(
this
,
'dispose'
);
delete
this
._itemMoveCommandDelegate;
,
// handles the commands fired by a single item
_itemMoveCommandHandler:
function
(sender, args)
debugger;
var
productItemId = args._dataItem.Id;
var
currentList =
this
._masterView.get_currentItemsList();
var
binder = currentList.getBinder();
switch
(args.get_commandName())
case
'moveUp'
:
this
._moveListItem(productItemId, binder,
"up"
, args._dataItem, args._itemElement, args._itemIndex);
break
;
case
'moveDown'
:
this
._moveListItem(productItemId, binder,
"down"
, args._dataItem, args._itemElement, args._itemIndex);
break
;
case
'moveTop'
:
this
._moveListItem(productItemId, binder,
"top"
, args._dataItem, args._itemElement, args._itemIndex);
break
;
case
'moveBottom'
:
this
._moveListItem(productItemId, binder,
"bottom"
, args._dataItem, args._itemElement, args._itemIndex);
break
;
,
_moveListItem:
function
(listItemId, binder, direction, dataItem, element, itemIndex)
binder.DataBind();
ProductCatalogSample.Web.Controls.ProductItemsMasterGridViewExtensions.registerClass(
'ProductCatalogSample.Web.Controls.ProductItemsMasterGridViewExtensions'
, Sys.Component);
if
(
typeof
(Sys) !==
'undefined'
) Sys.Application.notifyScriptLoaded();