1

I have this implementation:

    instance.web_calendar.CalendarView.include({
        handle_special_dates: function(date, cell){
            var def = $.Deferred(),
                model = new instance.web.Model('calendar.special');
            model.call('get_special_dates').then(function(result){
                // Transform date strings into date obj valueOf result.
                var new_obj = {}
                for (key in result){
                    if (result.hasOwnProperty(key)){
                        var dt_value = new Date(key).valueOf();
                        // Set new key as date.ValueOf() and use
                        // special date name as value.
                        new_obj[dt_value] = result[key]
                    }
                }
                return new_obj;
            }).then(function(result){
                // We stringify value, because object stringifies keys
                // automatically.
                var dt_value = String(date.valueOf())
                // Check if date value is in special dates keys.
                // We check for -1, because it actually returns index,
                // not a boolean value.
                if ($.inArray(dt_value, Object.keys(result)) != -1){
                    var div = $(
                        '<div />',
                        {
                            "class": 'fc-special-day',
                            text: result[dt_value]
                        }
                    )
                    cell.children('div').prepend(div)
                    // Set special day background
                    cell.addClass('fc-special-background')

                }
            })
            return def;
        },
        get_fc_init_options: function(){
            var self = this;
            fc_options = this._super()
            if (this.special_dates){
                return $.extend({}, fc_options, {
                    dayRender: function(date, cell){
                        self.handle_special_dates(date, cell)
                    }
                });
            } else {
                return fc_options;
            }
        },
    })
}

handle_special_dates function call method that is defined in backend of the system (this is asynchronous call), parses the result and uses it to check if we need to modify cell of the calendar.

The problem is that both then parts are called as many times as there are cells to check. So this is kind of clunky, because I need to call backend only once and then use result for all the cells.

Basically first then (the one that calls get_special_dates backend method) part should be called only once. I tried to separate that functionality, but then it is called later than dayRender needs it. In other words, it will never call when it is needed:).

Is there some way to somehow wait for first part to finish instead of calling it like it is now? I mean it is asynchronous call, but now I guess it is even slower than synchronous.

P.S. I know there is new Promise functionality in JS, but Odoo in this version currently uses jQuery deferred, so I'm using it too.

Update

To better explain the situation, I will try to describe the structure, what is currently done, and what the problem is.

This system has two parts, backend (managed by Python) and frontend (Javascript).

Backend manages data that can be used by frontend. To be specific, there is a method in backend called get_special_dates. It returns dates with names entered in specific table called calendar_special. So if someone enters any such date, it will return its date string representation (and name). For example we can enter date 2017-04-16 and call it Easter.

Now when Javascript part loads the calendar interface, it checks all currently visible calendar dates and compares with dates returned by get_special_dates. If compared dates match, it will mark them in calendar.

But the problem is, get_special_dates is now called as many times as there are checks for each date/cell pair. Let say if we have 30 dates in month and we are seeing 30 dates of that month, it would call get_special_dates 30 times, when in reality only one call is enough (because dates from backend are same for all date/cell pair checks). But I need to save result to be reused for all date/cell pairs (or some other way that is proper for deferred).

Basically it checks every visible calendar date and compares with what was entered in backend.

Maybe this picture makes more sense how this functionality work. Dates with green background are actually dates that were entered in backend as special dates.

enter image description here

Andrius
  • 19,658
  • 37
  • 143
  • 243
  • Avoid the [deferred antipattern](http://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! And you're not even resolving your `def` anywhere? – Bergi Apr 23 '17 at 16:53
  • @Bergi I'm new to deferred, so thats why this code can look weird. I myself know it is, just can't grasp how properly use deferred yet. – Andrius Apr 23 '17 at 17:13
  • It would be great, if you could suggest how to improve that code. – Andrius Apr 23 '17 at 17:18
  • @Roamer-1888 format of key is `YYYY-MM-DD`. At first I wanted to just return number that represent time from 1970, but there for some reason JS was also including timezone in date, so numbers were not matching between backend and JS when dates were actually the same. Though I'm looking for different kind of code improvement - with `deferred` being used properly. – Andrius Apr 24 '17 at 08:38
  • Do you want to process multiple date-cell pairs in one call? Or do you need to make multiple calls each for one date-cell pair? – Roamer-1888 Apr 24 '17 at 09:32
  • @Roamer-1888 having to call per date-cell pair is OK. What is not OK, that I'm now calling to get result of backend method as many times as there are call for each cell/date pair. I mean once I call `get_special_dates` (before calling any cell/date pairs), I already have special dates that I would be able to compare with all needed cells. Look for updated explanation, maybe it will explain a bit better. – Andrius Apr 24 '17 at 10:11

1 Answers1

1

What you are asking is effectively, "how do I cache a promise?".

The simple answer to which is "assign it to a member in a suitable scope, from which it can be accessed wherever needed."

Here, a "member in a suitable scope" (not necessarily the only one) is the object of which handle_special_dates() is a property, which (unless handle_special_dates() is called in an odd way) will be this from within handle_special_dates().

In addition to caching the promise, you can also perform the new_obj transform just once by chaining the first of your two thens at the point of caching.

specialDatesPromise: null, // not strictly necessary but makes the cache overt. 
handle_special_dates: function(date, cell) {
    // if promise doesn't already exist, call() and cache
    if(!this.specialDatesPromise) {
        this.specialDatesPromise = (new instance.web.Model('calendar.special')).call('get_special_dates')
        .then(function(specialDates) {
            // for economy of effort, perform transform at point of caching
            var new_obj = {};
            $.each(specialDates, function(key, value) {
                new_obj[new Date(key).valueOf()] = value;
            });
            return new_obj;
        });
    }
    // Now use the cached promise - it may be newly or previously cached - doesn't matter which.
    return this.specialDatesPromise.then(function(result) {
        var dt_value = String(date.valueOf());
        if (result[dt_value] !== undefined) {
            cell.addClass('fc-special-background').children('div').prepend($('<div/>', {
                'class': 'fc-special-day',
                'text': result[dt_value]
            }));
        }
    });
},

Further simplifications should work, barring mistakes/misunderstandings on my part.

Note, there's no need for jQuery.Deferred() because .call('get_special_dates') returns a viable promise.

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • Thanks, I'll check it when I get back home, though it looks like it will do a trick. – Andrius Apr 24 '17 at 13:36
  • Also good point about not using deferred and just using promise from `call`. Though for some reason I saw in Odoo standard source code, deferred is defined like I did and sometimes not, though `call` is used and it should not be necessary like you said. – Andrius Apr 24 '17 at 16:18
  • In the Odoo source, there's a reasonable chance they use `jQuery.Deferred()` to *create* a promise. In your code, you are a *consumer* of an Odoo-created promise. Big difference. On the other hand, the source could be badly written. – Roamer-1888 Apr 24 '17 at 16:45