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 loadedfunction 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 loadedfunction 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 latervar 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 loadedfunction 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();