dsuryd / dotNetify

Simple, lightweight, yet powerful way to build real-time web apps.
https://dotnetify.net
Other
1.17k stars 164 forks source link

problem when navigation property is introduced in model #9

Closed Ajitweb closed 8 years ago

Ajitweb commented 8 years ago

Hi Dicky,

Great utility ! I am still in the learning curve. My data model 'Company' contains the following navigation property public virtual ICollection Locations { get; set; }

In the view model (which inherits from BaseVM), I have the following property public IEnumerable Companys { get { return db.Companys.ToList();} }

On execution, I don't get the data in the browser. I have stepped through the code and the data is being populated at the View Model. As soon as I remove the navigation property from the Model, it works ! What might I be doing wrong?

Thanks

dsuryd commented 8 years ago

Hi, it could be that the Locations contains unserializable data (cyclic structure?). Look for thrown exceptions in the visual studio debug output; also check the browser console for error logs.

Ajitweb commented 8 years ago

Thanks for your reply Dicky. I can see the data in Locations when I step through the code and there are no related exceptions in the debug window nor the browser console. Anyway, I created a View model of only the fields that I require to pass to the view (I don't need the Locations data as of now) and so my problem is solved as of now.

Please expect more silly questions from me. Like currently, I am trying to mimic the tab('show') function of bootstrap's nav tablist through dotNetify. That is if I change a property at the server, a specific tab should be activated and its content shown. Currently I am able to change the class to "active" of the desired tab through dotNetify but this does not 'show' the tab content. Is it possible to execute js code by changing a property on the server?

dsuryd commented 8 years ago

Yes, you could use $on function to run JS function on server property change. See the "Job Queue" example.

Ajitweb commented 8 years ago

Awesome !. Thanks for giving power back to backend developers :)

dsuryd commented 8 years ago

I'm still interested on why you couldn't get the Locations working. If you have the chance, you can create and attach a small VS solution with that problem for me to take a look.

Ajitweb commented 8 years ago

Sure. I will prepare one and send to you. To which email shall I send? Really appreciate your willingness to solve my problem, Dicky .

Ajitweb commented 8 years ago

The file is more than 10MB and not allowed here, so I have made a link. https://we.tl/hEIRfHjekE

When you run the project, it should run normally and be able to view the company data. But if in CompanyVM.cs, you uncomment IEnumerable < Company > Companys property and comment out IEnumerable < CompanyViewModel > Companys property and then execute, you will not see the company data.

Also, how to have some kind of animation in the browser while Companys property is fetching the data.

Thanks Dicky.

dsuryd commented 8 years ago

There's a cyclic structure in the Company class. It references the Location class through the Locations property which has a reference back to the Company class through the Company property. That would cause serialization to fail. Your already did the correct thing to project the subset of the data into that CompanyViewModel class.

dsuryd commented 8 years ago

For loading animation, you can use a div styled with css animation and use visible binding on the div to a boolean view model property.

Ajitweb commented 8 years ago

Now I know what you meant by cyclic structure. Thanks. As I need those type of structures for my reports, I will continue use the subset data for VMs.

Thanks for the tip on loading animation. I did that by the text in the span element never gets displayed. Can you pls check. I have made another link. Thanks.

https://we.tl/Z31EG5sgqf

dsuryd commented 8 years ago

The reason it didn't work is because the code is synchronous. The simplest refactor would be to display the loading text as soon as the page is loaded, and then hide it when data is actually loaded:

        <span data-bind="visible: DataLoaded">Loading Company List</span>
        public bool DataLoaded
        {
            get { return Get<bool>(); }
            set { Set(value); }
        }

        public IEnumerable<CompanyViewModel> Companys
        {
            get
            {
                DataLoaded = true;
                return db.Companys.OrderBy(x => x.LongName).Select(x => new CompanyViewModel { CompanyID = x.CompanyID, LongName = x.LongName, ShortName = x.ShortName }).ToList();
            }
        }

Another way would be to use asynchronous programming, but only if there's a need.

BTW, your app requires DB - next time just comment it out and mock up the data, and don't include the binaries that can be fetched by NuGet.

Ajitweb commented 8 years ago

Thanks Dicky, for the explanation. For the time being, I will use the easy way and will visit async programming later on. Also noted the way I have to give you the project. Thanks.

Ajitweb commented 7 years ago

Hello Dicky, I used your refactor on the first page load. But for subsequent page loads (clicking on navigation buttons), I wanted to notifiy the user that the server is busy while it is fetching data.

This is the span

<div data-bind="visible:LoadList">
  <span> Loading Data Please wait....</span>
</div>

This is what I tried to do in the app.js file ; All navigation buttons has a class - 'fetchdata' In the click events of the navigation button, I removed the style (which KO puts when LoadList is false) This shows up the notification to the user and also the data is fetched and shown, But the notification does not disappear even though I change the value of LoadList to false on the server.

$(".fetchdata").click(function () {
$("*[data-bind*='visible:LoadList']").each(function (idx, element) { element.style.display = ""; });

I feel that, I have to change the value of LoadList property in javascript before the backend button function is called. Is this correct? and if yes, how do I change the property value in javascript. Thanks

dsuryd commented 7 years ago

Hi, I suggest you use vmCommand on the nav button click, so that you have access to LoadList property on the client-side. So it will look like this:

[html]
<div id="navButton1" data-bind="vmCommand: fetchData">...

[typescript]
class YourVMName {

  fetchData(item, element) {
      var vm: any = this;    

      vm.LoadList(true);    // Show the loading notification.

       ... // Call the server to load data.   
          // Server will then respond by setting LoadList to false to hide the notification.
   }
}

If you still have problem, send me your html and typescript or javascript files.

Ajitweb commented 7 years ago

Thanks Dicky. So in the vmCommand, i can put a name that can be called by the browser. I did not know that. So here is the javascript

var POVM = (function () {
    function POVM() {
    }
    POVM.prototype.fetchData = function (item, element) {
        var vm = this;
        vm.LoadList(true); // Show the loading notification.

        vm.PoListsPageNext(true);
    };
return POVM;

}());

I put a break point here and when I click on the nav button. It indeed executes the vm.LoadList(true); and I can see the 'Loading.." notification. But the next statement which is supposed to bring in the data from the server does not work

Here is the cs code

       public bool PoListsPageNext//Button property to go to next page
        {
            get { return false; }
            set
            {
                if (PoListsPage < PoListsPageCount)  //put break point here
                {
                    PoListsPage++;
                }
            }
        }

        public int PoListsPage  //Stores the current page number of Polists
        {
            get { return Get<int>(); }
            set
            {
                Set(value);
                Changed(() => Polists);
            }
       }

I put a breakpoint in the setter of PoListsPageNext - but execution does not reach here and there are no errors reported. What am I doing wrong? Thanks.

dsuryd commented 7 years ago

After the first call to vm.PoListsPageNext(true), the value will no longer be sent to the server unless it changes. So in the server, in the setter for PoListsPageNext, just add Changed( () => PoListsPageNext ), which will cause the property value on the client reset to false.

Ajitweb commented 7 years ago

Thanks Dicky for that explanation.

So I will refactor my code to change the page number in javascript (rather than at the view model) thereby avoiding the need to put the Changed(()...) . I have done that, as a test, for one of the nav buttons (next button). And all looks well.

But another very strange problem. The Loadlist does not set to false at random times. What I mean is - after the first set of records are displayed, I click on the nav button to get the next set of records - then the next set of records are shown and again I click on the nav button to get another set on so on. Sometimes (randomly), after the next set of records are fetched and shown on screen, the notification icon does not disappear (that means Loadlist value is still true)- creating an effect that the server is still busy. Sometimes this happens on the first click of the nav button - sometimes on the 3rd etc. What I mean is - it is very random. And to top it - there were times where this problem does not occur at all. Please see the linked project (with mockup data) and let me know, if you face the same issue.

https://we.tl/hlqZ3hWl2x

Thanks.

dsuryd commented 7 years ago

My guess that there's a race condition between LoadList being set to true by the client, and the server setting it to false. Since those two events are asynch, it could happen that the first occurs after the second, which would lead to the bug.

I have a better solution for you. Displaying loading indicator is more of a UI concern, and doesn't need server round-trip to manage it. Use a local observable for it, just like in my BetterList example. Specifically, in your test code:

[html] <span data-bind="visible:_loadList"><i class="fa fa-spinner fa-spin"></i></span>

[app2.js]

var POVM = (function () {
   function POVM() {
     // Declare local observable.  This won't get sent to the server.
      this._loadList = true;
    }
    POVM.prototype.$ready = function () {
        var vm = this;
        vm._loadList(false);
        vm.$on(vm.TabActivate, function (iArgs) { return vm.showTab(iArgs); });  
    };

    POVM.prototype.showTab = function (tab) {
        $('.nav-tabs a[href="#' + tab + '"]').tab('show');
    };

    POVM.prototype.fetchData = function (item, element) {
        var vm = this;
        vm._loadList(true); // Show the loading notification.
        var nextPage = vm.PoListsPage() + 1;
        vm.PoListsPage(nextPage);

       // Once Polists data is received, hide the loading notification.
        vm.$once(vm.Polists, function () { vm._loadList(false) });
    };

    return POVM;
}());

BTW, if you set dotnetify.debug = true at the beginning of your app2.js, you can see the data being passed back and forth in the browser console log.

Ajitweb commented 7 years ago

Perfect, Dicky. Thank you!. Additionally, I put logical operators for both the server and local 'loadlist' observables - so it takes care during initial page load and subsequent nav button clicks.

So I learned how to declare local observables, what that vm.$once stands for and how to see the data being passed back and forth.

Thank you once again for being so patient with me.

The 'big' plan is have various modules prepared (like the PO module that I am currently working on) and somehow give a seamless experience to the user - where they can jump from one module to another without having to save the changes in the either module - until they (the user) is ready - Then it will similar to the desktop ERP that I have built over the years. I guess - I will have to study thoroughly your Dashboard example for my plan to work. Thanks once again.

Ajitweb commented 7 years ago

Hi Dicky, I am facing an issue with a feedback mechanism that I copied from your LiveChart example. After some time (the time varies), the app.js stops receiving the data from the server. The process on the server does not stop - but the updateChart method in js file does not receive the data.

In fact, I am also facing the same problem with LiveChart example when I run it from your site. First time it ran for 1224 seconds and then no feedback. Next time at 1216, next time at 513, then at 26, then at 1223. I tried it on another PC - still the same result.

In my example, I am simply providing a feedback of the number of records processed by the server. There are around 8500 records. I am providing feedback after every record is processed. But after processing a few thousand records, the js stops receiving the data from the server.

As the process is long, I tend to work on other things on the PC like VS or email etc. So technically, I have not seen the pause happening at that exact time.

Anything in SignalR that I have to be worried about or to look out for?

Thanks

dsuryd commented 7 years ago

Hi Ajit, thank's for bringing this up. I saw the same issue on the live chart after 20 minutes. My suspicion is with SignalR reconnection strategy, but I will investigate this thoroughly.

dsuryd commented 7 years ago

Ajit, the server session is set to expire after 20 mins when there's no activity from the client. Try putting setInterval in your js to ping your server view model before this expiration time. Other way is to set VMController.CacheExpiration.

Ajitweb commented 7 years ago

Thanks Dicky, I did the following in js

 POVM.prototype.$ready = function () {
        var _this = this;
        var myVar = setInterval(function () { _this.pingServer(); }, 600000); //Ping every 10 minutes.
    };
    POVM.prototype.pingServer = function () {
        var vm = this;
        vm.POKeepMeAlive("KeepMeAlive"); 
    };

and this on the model

        public string POKeepMeAlive
        {
            get { return Get<string>(); }
            set
            {
                Set("YouAreAlive");  
                Changed(() => POKeepMeAlive); 
            }
        }
Ajitweb commented 7 years ago

Hello Dicky, Do you have an example of VMController.CacheExpiration ? Thanks.

dsuryd commented 7 years ago

Hi, I dont have it in the example, but see the class source code in the github. It is just a static TimeSpan prop. Did the setInterval work?

On Saturday, October 22, 2016, Ajitweb notifications@github.com wrote:

Hello Dicky, Do you have an example of VMController.CacheExpiration ? Thanks.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/dsuryd/dotNetify/issues/9#issuecomment-255570989, or mute the thread https://github.com/notifications/unsubscribe-auth/AOS8ks-gjwDL0LudsM5G9jAIJ9mdXrnoks5q2vQSgaJpZM4JIHCm .

Ajitweb commented 7 years ago

Hello Dicky,

I am trying to attach a datepicker to a field - but the changed value (either through the datepicker control or manual typing) is not being passed back to the vm. I tried to capture the change event of the datepicker in javascript and manually store it in the vm - but it is not behaving properly.

Which date picker control do you suggest that will work properly with dotnetify. I tried the control given here http://eonasdan.github.io/bootstrap-datetimepicker/Events/#dpchange

Thanks.

Ajitweb commented 7 years ago

Yes Dicky, the setInterval worked! - Please review the code that I have posted above your reply.

dsuryd commented 7 years ago

I think that datepicker is a good one. What you attempted to do with capturing the change event is in the right direction. Why didn't it work?

Ajitweb commented 7 years ago

Hello Dicky, Now it is working. In the change event of the datepicker, I was simply returning the raw date. Actually, I had formatted the date to dd/MM/yyyy in the control. Luckily, the datepicker control uses moment.js, so it was easy to format (thanks to many answers to similar questions at stackoverflow)

Here is the working code. Please review and do suggest if any improvements can be made.

HTML

                                <div class='input-group date datetimepicker'>
                                    <input type="date" class="form-control" data-bind="value: pomast_vdate">
                                    <span class="input-group-addon">
                                        <span class="glyphicon glyphicon-calendar"></span>
                                    </span>
                                </div>

Javascript to initialise

$(document).ready(function () {
    $('.datetimepicker').datetimepicker({
        format: 'DD/MM/YYYY'
    });
});

Javascript to capture the change event

    POVM.prototype.$ready = function () {
        var vm = this;
        $('.datetimepicker').on("dp.change", function (e) {
            var databindvalue = this.firstElementChild.getAttribute("data-bind").substring(7)  //Get the value from 'value' name of data-bind attribute.
            vm[databindvalue](moment(e.date).format('DD/MM/YYYY'));

        });
dsuryd commented 7 years ago

An improvement to this is to make a custom Knockout binding for the dates. The logic that convert the date format can just reside inside the binding handler. There are examples in Knockout website on this.

Ajitweb commented 7 years ago

Hi Dicky, I have applied a input mask on elements that is input as numbers.

<input type="text" class="form-control" id="pomastexrate" data-bind="value: pomast_exrate" >

$("#pomastexrate").autoNumeric(
            'init',
            {
                aSep: ',',
                aDec: '.',
                aSign: "$ ",
                mDec: 7
            }
        );

But the data is not being sent back to server when the control loses focus. I have to manually send it back via the blur function. Is there any other way?

Thanks.

dsuryd commented 7 years ago

Hi Ajit, haven't had the chance to experiment with it, but I think that autoNumeric plugin is interfering with Knockout binding. An elegant solution would be to write a custom Knockout binding that works with this autoNumeric plugin. I found something in StackOverflow. Look at this one: http://stackoverflow.com/questions/27406965/knockoutjs-autonumeric-binding-handler-replace-empty-value-with-0

dsuryd commented 7 years ago

By the way, could you just create new issues in the future for better organization and benefit other readers. Thanks!

Ajitweb commented 7 years ago

Thanks Dicky. Will have a go at the custom Knockout binding. Also, noted about the point on creating new issues.

Thanks once again.

Ajitweb commented 7 years ago

Just to complete our discussion above, here is the custom Knockout binding for datetimepicker.

ko.bindingHandlers.datepickerko = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        $(element).datetimepicker({
            format: 'DD/MM/YYYY'
        });

        //when a user changes the date, update the view model
        ko.utils.registerEventHandler(element, "dp.change", function (event) {
            var value = valueAccessor();
            if (ko.isObservable(value)) {
                value(moment(event.date).format('DD/MM/YYYY'));
            }
        });
    }
};

and the HTML

<input type="text" id="pomastvdate" class="form-control" data-bind="value: pomast_vdate, datepickerko: pomast_vdate> The property in the view model is of the string type and I have to parse the values to (and from) .net datetime type in the main model.