+1-888-365-2779
Try Now
More in this section

Forums / General Discussions / How to implement moving of items up and down?

How to implement moving of items up and down?

9 posts, 0 answered
  1. Anton Mernov
    Anton Mernov avatar
    110 posts
    Registered:
    03 Dec 2008
    16 Mar 2011
    Link to this post
    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:
    http://localhost:60876/Sitefinity/Services/Pages/PagesService.svc/batch/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.
  2. George
    George avatar
    28 posts
    Registered:
    24 Sep 2012
    21 Mar 2011
    Link to this post
    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
  3. Anton Mernov
    Anton Mernov avatar
    110 posts
    Registered:
    03 Dec 2008
    22 Mar 2011
    Link to this post
    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.
  4. George
    George avatar
    28 posts
    Registered:
    24 Sep 2012
    28 Mar 2011
    Link to this post
    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
  5. Denis
    Denis avatar
    105 posts
    Registered:
    27 Feb 2009
    25 Nov 2011
    Link to this post
    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.
  6. Radoslav Georgiev
    Radoslav Georgiev avatar
    3370 posts
    Registered:
    01 Feb 2016
    25 Nov 2011
    Link to this post
    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
  7. Denis
    Denis avatar
    105 posts
    Registered:
    27 Feb 2009
    25 Nov 2011
    Link to this post
    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.
  8. Saeed
    Saeed avatar
    14 posts
    Registered:
    21 Jun 2011
    29 Nov 2011
    Link to this post
    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!
  9. Radoslav Georgiev
    Radoslav Georgiev avatar
    3370 posts
    Registered:
    01 Feb 2016
    01 Dec 2011
    Link to this post
    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
9 posts, 0 answered