0

I have code only component, where I generate my menu, where I got around 700 records where all of them are loaded at once. I decided to recreate it and load another menu objects to menu on click event.

Here is my viewmodel for my menu, I removed some not related parts from my viewmodel

public class CategoryMenuViewModel : DotvvmViewModelBase
{
    public static CategoryMenuFacade MenuFacade { get; private set; }

    public static List<CategoryMenuDTO> CategoryMenuList { get; set; } = new List<CategoryMenuDTO>();
    //private static bool LoadMoreCategories { get; set; }

    public CategoryMenuViewModel(CategoryMenuFacade categoryMenuFacade)
    {
        MenuFacade = categoryMenuFacade;
    }

    public override Task PreRender()
    {
        if (!Context.IsPostBack)
        {
            CategoryMenuList.AddRange(CategoryMenuFacade.GetCategoryMenu(CategoryId));
            //CategoryMenuList = MenuFacade.GetCategoryMenu();
        }

        return base.PreRender();
    }

    public static int? CategoryId { get; set; }
    public static List<int> PreviousCategoryId { get; set; } = new List<int>();

    [AllowStaticCommand]
    public static List<CategoryMenuDTO> SelectedCategory()
    {

        if (CategoryId == null) return CategoryMenuList;
        bool idExist = PreviousCategoryId.Any(r => r == CategoryId);
        if (!idExist)
        {
            PreviousCategoryId.Add((int)CategoryId);
            CategoryMenuList.AddRange(MenuFacade.GetCategoryMenu(CategoryId));
        }
        return CategoryMenuList;
    }
}

My previous thought was that I can load another parts of menu using this script where I get object Id on which I clicked and send it to viewmodel. It works fine, but problem with this approach is, that it fires PreRender() method and loads all data again from another viewmodels.

dotvvm.events.beforePostback.subscribe(function (data) {
try {
    var selectedCategory = $(event.target.lastChild);
    var nodeId = selectedCategory[0].attributes.Id.nodeValue;
    var idNumber = nodeId.substring(nodeId.indexOf("_") + 1);
    data.viewModel.CategoryMenuViewModel().CategoryId = idNumber;

} catch (error) {
    // continue with error
}});

My other thought was about using knockout binding handler where I am able to update my CategoryId property in viewModel, but I dont know exactly, how I can fire my some method in viewModel or even code-only-component to render new data.

ko.bindingHandlers["click"] = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    $(element)
        .on('click', function (e) {
            var selectedCategory = $(e.target.lastChild);
            var nodeId = selectedCategory[0].attributes.Id.nodeValue;
            var idNumber = parseInt(nodeId.substring(nodeId.indexOf("_") + 1));
            //data.viewModel.CategoryMenuViewModel().CategoryId = idNumber;
            // if someone deletes the value from the textbox, set null to the viewmodel property
            var prop = valueAccessor();

            if (ko.isObservable(prop.CategoryItemId)) {
                prop.CategoryId(idNumber);
            }

        });
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    // get the value from the viewmodel
    var value = ko.unwrap(valueAccessor());
    var test = viewModel.CategoryId();
}};

Here is my view

<cc:CategoryMenu DataContext="{{value: CategoryMenuViewModel}}" class="categorieMenu" ID="mainMenu"/>

And .dotcontrol file

<coc:CategoryMenu DataSource="{{value: CategoryMenuViewModel.CategoryMenuList}}"
                  ActiveCategory="{{controlProperty: ActiveCategory}}"
                  SelectedValue="{{value: CategoryMenuViewModel.CategoryId}}"
                  ItemValueBinding="{{value:Id}}"
                  data-bind="click: {CategoryId}"
                  Click="{{staticCommand: CategoryMenuViewModel.SelectedCategory()}}"/>

Short preview of properties in my coc

    public static readonly DotvvmProperty ChangedProperty
        = DotvvmProperty.Register<Command, CategoryMenu>(c => c.Changed, null);
    public Command Changed
    {
        get => GetValue(ChangedProperty) as Command;
        set => SetValue(ChangedProperty, value);
    }
    public int SelectedItemId
    {
        get => (int)GetValue(SelectedItemIdProperty);
        set => SetValue(SelectedItemIdProperty, value);
    }
    public static readonly DotvvmProperty SelectedItemIdProperty
        = DotvvmProperty.Register<int, CategoryMenu>(c => c.SelectedItemId);

So my question is, how I can handle changed event to fire method which will load another parts of my menu and render it using my component?

Martin
  • 455
  • 1
  • 11
  • 34
  • Have you looked at the static commands? They don't transfer the entire viewmodel and don't call the `Init`, `Load` and `PreRender` methods. https://www.dotvvm.com/docs/tutorials/basics-static-command-binding/latest Also, it can return results and update a property in the viewmodel with it. You could use something like `{staticCommand: Children = LoadChildren(_this.Id)}`. – Tomáš Herceg Jan 06 '18 at 10:00
  • I used this also, but problem was with creating instance of `CategoryMenuFacade` which was always null in `CategoryMenuViewModel constructor`. We are using `Castle Windsor` and I dont know how to fix it. It must have something to do with `staticCommand` – Martin Jan 08 '18 at 13:02
  • How the method looks like? Is it static, or an instance one? – Tomáš Herceg Jan 09 '18 at 18:52
  • I updated answer to current state and fixed issue with `CategoryMenuFacade ` not being initialized. Anyway after this another problem occured with my `bindingHandler`. It wont fire at all so I never get Id of object on which I clicked and my `data-bind="click: {CategoryId}"` not working at all. – Martin Jan 10 '18 at 08:43
  • Oh, I see - the CategoryMenuFacade is static, but IoC/DI containers typically ignore static members and static members. In DotVVM 1.x, the only way is to resolve the dependency by yourself. `HttpContext.Current.GetOwinContext().GetDotvvmContext().Services.GetService()` In DotVVM 2.0, we will have a way to invoke static commands on instance services which will support DI. And the static properties and things like that will not be filled too - you need to pass all the information as a parameter in the static command method. – Tomáš Herceg Jan 13 '18 at 17:52
  • And is there a way how to handle `click event` using `bindingHandler` with `staticCommand`? Right now I have handler which updates my VM property with new `CategoryId` anytime I perform click on any part of my menu. Problem is that using `staticCommand` completly ignores my `data-bind` so I never get that `CategoryId` and it always is `null`. What cause this behaviour? – Martin Jan 17 '18 at 12:07
  • You need to pass the `CategoryId` as a parameter to the function. Static command transfers only the function arguments to the server, nothing else. – Tomáš Herceg Jan 19 '18 at 17:46

0 Answers0