Serhioromano / bootstrap-calendar

Full view calendar with year, month, week and day views based on templates with Twitter Bootstrap.
http://bootstrap-calendar.eivissapp.com/
MIT License
3.02k stars 1.29k forks source link

Asynchronous event get #150

Open dzoeteman opened 11 years ago

dzoeteman commented 11 years ago

Instead of making the AJAX request synchronous, which can lock up the browser window if the server takes too long, please make the get event request asynchronous with callbacks instead.

This can prove major trouble in the case of where the server takes too long to respond, and for example, Google Chrome will tell you that the page is stuck.

mlocati commented 11 years ago

:thumbsup: We'd need to tell users that the ajax is running, and avoid further user interaction. What about showing a div with 100% width and height, a cursor:wait css property, an opacity of 50% and an animated gif image (for instance one from www.ajaxload.info)?

dzoeteman commented 11 years ago

What about spin.js? ( http://fgnass.github.io/spin.js/)

Serhioromano commented 10 years ago

Spin looks good. The only problem I would not want to add one more dependency. Although this one looks cool.

@mlocati I agree that calendar context may be simply darken during event load or view change. But I think that will not solve this problem

This can prove major trouble in the case of where the server takes too long to respond, and for example, Google Chrome will tell you that the page is stuck.

No matter if it sync or async it will fail if it is too long.

mlocati commented 10 years ago

Spin looks good. The only problem I would not want to add one more dependency.

I agree. What about standard animated gif image?

@mlocati I agree that calendar context may be simply darken during event load or view change. But I think that will not solve this problem

Well, I'd made it 100% of the screen, not the context: this way the users can't click other controls.

No matter if it sync or async it will fail if it is too long.

Yes, but the user experience is really bad on sync calls: the whole page freezes and it seems to the users that nothing is happening, except a browse freeze.

dzoeteman commented 10 years ago

How about you include options before and after event callbacks for users, so you can implement a loading gif or spin.js yourself?

mlocati commented 10 years ago

That would be nice: this could lead to a better integration with the page in which the calendar is in. We could add an event "onBusyChange": by defaults it's handled internally, but the page could use it to show/hide the "loading" element.

dzoeteman commented 10 years ago

Yeah, I think that would be the best option. I'm not too fond with AJAX myself, so someone else will have to make it, but it would be really nice.

Serhioromano commented 10 years ago

How about you include options before and after event callbacks for users, so you can implement a loading gif or spin.js yourself?

Perfect! And by default we will use simple lock with gif. Thene very user will be able to adopt lok to style that is used through all site. @gdscei - genius.

nderkach commented 10 years ago

I think async event loading makes a lot of sense when you query calandar data from the backend and the data is retrieved on the fly. If made synchronous, this causes major UI freezes and significantly deteriorates UX.

Any of you guys actually made async AJAX work? Would be nice to see the code.

Serhioromano commented 10 years ago

We will

magnusburton commented 10 years ago

Would love to be able to fetch data without the need of async

bjrhodes commented 10 years ago

Hi, I needed async event fetching too, so I was going to make a pull request for optionally returning a jquery promise for events_source. When looking into it I found a quick and dirty workaround though. I may still go the promises route, but for anyone else wanting something similar here's a quick stripped down example of loading events asynchronously with the current implementation which works by stalling the call to events_source until your events are (async) loaded:

(function(){
    var events = [];
    var options = {
        /* All your stuff here */
        events_source: function() {
            return events;
        },
        onBeforeEventsLoad: function(done) {
            var url = "/myurl/";
            var params = {
                "from": this.options.position.start.getTime(),
                "to": this.options.position.end.getTime(),
                "something" : "requires user input or whatever"
            };
            // maybe the above needs to read some form fields or something?
            $.get(url, params)
                .done(function(data) {
                    if (data.success && data.result && (data.result instanceof Array)) {
                        events = data.result;
                    }
                    done();
                    calendar._render();
                }).fail(function() {
                    // maybe set an error state?
                    events = [];
                    done();
                    calendar._render();
                });
        },
    };

    var calendar = $('#calendar').calendar(options);
})();

Note; this uses internals, hence quick-and-dirty. Also it exposes a more serious bug around the internal module logic, which is that there is a secret and seemingly ignored async call between _loadEvents() and _render() where _loadEvents is waiting on a callback to continue. This sets up a race condition, hence the need to explicitly call _render() in the above.

daddykotex commented 9 years ago

I had the same issue, but I went for another strategy. I changed the loader() definition so it accepts a callback. This callback is called when the events are retrieved. Of course the calendar doesn't render until the events are fetched, but onBeforeEvents is not blocked. That wa I can use this function to display a loading gif or something on my page.

Here is the relevant code I changed, maybe you can tell me if there is something wrong:

The view functions takes a callback:

        [...]
        this._loadEvents(function(calendar){
            calendar._render();
            calendar.options.onAfterViewLoad.call(this, calendar.options.view);
        });
        [...]
        Calendar.prototype._loadEvents = function(callback) {

        [...]

        var loader;
        switch($.type(source)) {
            case 'function':
                loader = function(callback) {
                    return callback(source(self.options.position.start, self.options.position.end, browser_timezone));
                };
                break;
            case 'array':
                loader = function(callback) {
                    return callback([].concat(source));
                };
                break;
            case 'string':
                if(source.length) {
                    loader = function(callback) {
                        var events = [];
                        var params = {from: self.options.position.start.getTime(), to: self.options.position.end.getTime()};
                        if(browser_timezone.length) {
                            params.browser_timezone = browser_timezone;
                        }
                        $.ajax({
                            url:      buildEventsUrl(source, params),
                            dataType: 'json',
                            type:     'GET',
                            async:    true
                        }).done(function(json) {
                            if(!json.success) {
                                $.error(json.error);
                            }
                            if(json.result) {
                                events = json.result;
                            }
                            callback(events);
                        });
                    };
                }
                break;
        }
        //the onBeforeCall then looks like this
        this.options.onBeforeEventsLoad.call(this, function() {
            loader(function(events){
                self.options.events = events;

                self.options.events.sort(function(a, b) {
                    var delta;
                    delta = a.start - b.start;
                    if(delta == 0) {
                        delta = a.end - b.end;
                    }
                    return delta;
                });
                self.options.onAfterEventsLoad.call(self, self.options.events);
                callback(self);
            });

        });

Works but nothing is displayed until the events are loaded.