Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / sync.js @ edd1d565

History | View | Annotate | Download (8.3 kB)

1
;(function(root){
2
    
3
    // root
4
    var root = root;
5
    
6
    // setup namepsaces
7
    var snf = root.synnefo = root.synnefo || {};
8
    var sync = snf.sync = snf.sync || {};
9
    var api = snf.api = snf.api || {};
10

    
11
    // shortcuts
12
    var bb = Backbone;
13

    
14
    // logging
15
    var logger = new snf.logging.logger("SNF-API");
16
    var debug = _.bind(logger.debug, logger)
17
    
18
    // method map
19
    var methodMap = {
20
        'create': 'POST',
21
        'update': 'PUT',
22
        'delete': 'DELETE',
23
        'read'  : 'GET'
24
    };
25

    
26
    // custom getUrl function
27
    // handles url retrieval based on the object passed
28
    // on most occasions in the synnefo api this will call
29
    // the model/collection url method
30
    var getUrl = function(object, options) {
31
        if (!(object && object.url)) return null;
32
        return _.isFunction(object.url) ? object.url(options) : object.url;
33
    };
34
    
35
    // Call history (set of api paths with the dates the path last called)
36
    var api_history = api.requests = api.requests || {};
37
    var addApiCallDate = function(url, d, method) {
38
        if (d === undefined) { d = Date() };
39
        var path = snf.util.parseUri(url).path;
40
        api_history[path + "_" + method] = d;
41
        return api_history[path]
42
    }
43
    
44
    var api_errors = api.errors = api.errors || [];
45
    var add_api_error = function(settings, data) {
46
        api_errors.push({url:settings.url, date:new Date, settings:settings, data:data})
47
    }
48

    
49
    var setChangesSince = function(url, method) {
50
        var path = snf.util.parseUri(url).path;
51
        var d = api_history[path + "_" + method];
52
        if (d) {
53
            url = url + "?changes-since=" + snf.util.ISODateString(d)
54
        }
55
        return url;
56
    }
57
    
58
    // custom sync method
59
    // appends global ajax handlers
60
    // handles changed-since url parameter based on api path
61
    api.sync = function(method, model, options) {
62

    
63
        var type = methodMap[method];
64
        
65
        if (model && (model.skipMethods || []).indexOf(method) >= 0) {
66
            throw "Model does not support " + method + " calls";
67
        }
68
        
69
        if (!options.url) {
70
            options.url = getUrl(model, options) || urlError();
71
            options.url = options.refresh ? options.url : setChangesSince(options.url, type);
72
            if (!options.refresh && options.cache === undefined) {
73
                options.cache = true;
74
            }
75
        }
76

    
77
        options.handles_error = options.handles_error || false;
78
        
79
        if (api.stop_calls) {
80
            return;
81
        }
82

    
83
        var success = options.success || function(){};
84
        var error = options.error || function(){};
85
        var complete = options.complete || function(){};
86
        var before_send = options.beforeSend || function(){};
87

    
88
        // custom json data.
89
        if (options.data && model && (method == 'create' || method == 'update')) {
90
            options.contentType = 'application/json';
91
            options.data = JSON.stringify(options.data);
92
        }
93
        
94
        var api_params = {};
95
        var api_options = _.extend(api_params, options, {
96
            success: api.handlerWrapper(api.successHandler, success, "success"),
97
            error: api.handlerWrapper(api.errorHandler, error, "error"),
98
            complete: api.handlerWrapper(api.completeHandler, complete, "complete"),
99
            beforeSend: api.handlerWrapper(api.beforeSendHandler, before_send, "beforeSend"),
100
            cache: options.cache || false,
101
            timeout: options.timeout || window.TIMEOUT || 5000
102
        });
103
        return bb.sync(method, model, api_options);
104
    }
105
    
106
    api.handlerWrapper = function(wrap, method, type) {
107
        var cb_type = type;
108
        return function() {
109
            
110
            if (type == "error") {
111
                add_api_error(this, arguments);
112
            }
113

    
114
            if (type == "error" && this.handles_error) { return method.apply(this, arguments)}
115

    
116
            var args = wrap.apply(this, arguments);
117
            args = _.toArray(args);
118
            var ajax_options = this;
119
            
120
            try {
121
                if (args[1] === "abort") {
122
                    api.trigger("abort");
123
                    return;
124
                }
125
            } catch(error) {
126
                console.error("error aborting", error);
127
            }
128

    
129
            // FIXME: is this good practice ??
130
            // fetch callbacks wont get called
131
            try {
132
                // identify xhr object
133
                var xhr = args[2];
134
                if (args.length == 2) {
135
                    xhr = args[0];
136
                }
137

    
138
                // do not call success for 304 responses
139
                if (args[1] === "notmodified" || xhr.status == 0 && $.browser.opera) {
140
                    if (args[2]) {
141
                        addApiCallDate(this.url, new Date(args[2].getResponseHeader('Date')), ajax_options.type);
142
                    }
143
                    return;
144
                }
145
                
146
            } catch (err) {
147
                console.error(err);
148
            }
149
            method.apply(this, args);
150
        }
151
    }
152

    
153
    api.successHandler = function(data, status, xhr) {
154
        //debug("ajax success", arguments)
155
        // on success, update the last date we called the api url
156
        addApiCallDate(this.url, new Date(xhr.getResponseHeader('Date')), this.type);
157
        return [data, status, xhr];
158
    }
159

    
160
    api.errorHandler = function(event, xhr, settings, error) {
161
        //debug("ajax error", arguments, this);
162
        arguments.ajax = this;
163
        
164
        // skip aborts
165
        if (xhr != "abort") {
166
            if (!settings.handles_error) api.trigger("error", arguments);
167
        }
168
        return arguments;
169
    }
170

    
171
    api.completeHandler = function(xhr, status) {
172
        //debug("ajax complete", arguments)
173
        return arguments;
174
    }
175

    
176
    api.beforeSendHandler = function(xhr, settings) {
177
        //debug("ajax beforeSend", arguments)
178
        // ajax settings
179
        var ajax_settings = this;
180
        return arguments;
181
    }
182

    
183

    
184
    api.call = function(url, method, data, complete, error, success) {
185
            var self = this;
186
            error = error || function(){};
187
            success = success || function(){};
188
            complete = complete || function(){};
189
            var extra = data ? data._options || {} : {};
190
            if (data && data._options) { delete data['_options'] };
191

    
192
            var params = {
193
                url: snf.config.api_url + "/" + url,
194
                data: data,
195
                success: success,
196
                complete: function() { api.trigger("call"); complete(this) },
197
                error: error
198
            }
199

    
200
            params = _.extend(params, extra);
201
            this.sync(method, this, params);
202
        },
203

    
204
    _.extend(api, bb.Events);
205

    
206
    
207
    api.updateHandler = function(options) {
208
        this.cb = options.callback;
209
        this.limit = options.limit;
210
        this.timeout = options.timeout;
211

    
212
        this.normal_timeout = options.timeout;
213
        this.fast_timeout = options.fast;
214

    
215
        this._called = 0;
216
        this.interval = undefined;
217
        this.call_on_start = options.call_on_start || true;
218

    
219
        this.running = false;
220
        
221
        // wrapper
222
        function _cb() {
223
            if (this.fast_timeout == this.timeout){
224
                this._called++;
225
            }
226

    
227
            if (this._called >= this.limit && this.fast_timeout == this.timeout) {
228
                this.timeout = this.normal_timeout;
229
                this.setInterval()
230
            }
231
            this.cb();
232
        };
233

    
234
        _cb = _.bind(_cb, this);
235

    
236
        this.faster = function() {
237
            this.timeout = this.fast_timeout;
238
            this._called = 0;
239
            this.setInterval();
240
        }
241

    
242
        this.setInterval = function() {
243
            this.trigger("clear");
244
            window.clearInterval(this.interval);
245
            this.interval = window.setInterval(_cb, this.timeout);
246
            this.running = true;
247
            if (this.call_on_start) {
248
                _cb();
249
            }
250
        }
251

    
252
        this.start = function () {
253
            this.setInterval();
254
        }
255

    
256
        this.stop = function() {
257
            this.trigger("clear");
258
            window.clearInterval(this.interval);
259
            this.running = false;
260
        }
261
    }
262
    
263
    api.stop_calls = false;
264

    
265
    // make it eventable
266
    _.extend(api.updateHandler.prototype, bb.Events);
267
    
268
})(this);