How to implement moving of items up and down?

Posted by Community Admin on 03-Aug-2018 07:34

How to implement moving of items up and down?

All Replies

Posted by Community Admin on 16-Mar-2011 00:00

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"));

i.e. I've added to additional menu items: MoveUp and MoveDown. MoveUpComandName is "moveUp", MoveDownCommandName is "moveDown".

If I move up a page Fiddler shows the request to the following Url:
localhost:60876/.../move/?managerType=Telerik.Sitefinity.Modules.Pages.PageManager&providerName=&itemType=Telerik.Sitefinity.Pages.Model.PageNode&hierarchyMode=true&sortExpression=Title%20ASC&direction=up

However, I do not see the similar request to my WCF service when I try to move an item in my module.

Is there any specific command names for these actions? How can I achieve it?

Thanks in advance,
Anton.

Posted by Community Admin on 21-Mar-2011 00:00

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;

This is how the extension file structure may look like,

// 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;
    ,

And this is how we've done the moveUp handling for pages,

_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;
    
,

The _movePage method includes,
_movePage: function (pageId, binder, direction)
    if (binder.canMoveNode(pageId, direction))
        binder.moveNode(pageId, direction);
    
    else
        this._showCantMoveMessage(direction);
    
,


Hope this helps.


Greetings,
George
the Telerik team

Posted by Community Admin on 22-Mar-2011 00:00

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.

Posted by Community Admin on 28-Mar-2011 00:00

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

The content listing uses RadGridBinder which bindes RadGrid, so this should be also helpful,

http://www.telerik.com/help/aspnet-ajax/grid-overview.html

This is how MovePageNode is implemented in the OpenAccessPageProvider.cs for pages,
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();
    

And it's wrapper in the PageManager.cs,
/// <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);
        
    );

Your model should inherit from IOrderedItem,
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;
        
    

Hope this helps.

Kind regards,
George
the Telerik team

Posted by Community Admin on 25-Nov-2011 00:00

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);
        
    

Bolded part fails with the following error:
Line: 731 Error: Unable to get value of the property 'apply': object is null or undefined

Per my investigations calling base 'initialize' method fails because there is no base class found for this object in thecallBaseMethod ->_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)

Please let me know what I did wrong.

Thanks,
Denis.

Posted by Community Admin on 25-Nov-2011 00:00

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

Do you want to have your say in the Sitefinity development roadmap? Do you want to know when a feature you requested is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items

Posted by Community Admin on 25-Nov-2011 00:00

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);

I attached screenshot of grid's actions.

Global search for GetScriptReferences and GetScriptDescriptors in the custom module project didn't return anything.
I checked ProductsModule in the SDK and found these functions in 
CustomSettingsDesignerView : ContentViewDesignerView
class.

Is this represents settings on the side? I don't use these settings in my module. Should I?

Thanks,
Denis.

Posted by Community Admin on 29-Nov-2011 00:00

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!

Posted by Community Admin on 01-Dec-2011 00:00

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"
);

2) You need to register the custom extension script:
Copy Code
var scripts = new Dictionary<string, string>();
 
scripts.Add(
    string.Format("0, 1",
    ProductsDefinitions.ProductItemsMasterGridViewExtensionScript, typeof(ProductsModule).Assembly.FullName),
    "OnMasterViewLoaded");
productsGridView.ExternalClientScripts = scripts;

Copy Code
private static readonly string ProductItemsMasterGridViewExtensionScript = "ProductCatalogSample.Web.Controls.ProductItemsMasterGridViewExtensions.js";

3) Here is the script. You have to wire your custom handler to the item command event on the items grid. Then you must add custom logic for changing ordinals:
Copy Code
// 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();


4) You should set the sort expression of your items grid to be by ordinal and not by title for example.


Kind regards,
Radoslav Georgiev
the Telerik team
Do you want to have your say in the Sitefinity development roadmap? Do you want to know when a feature you requested is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items

This thread is closed