Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (8.6 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
            var status = 304;
115
            if (arguments[0]) {
116
                var status = arguments[0].status;
117
            }
118

    
119
            if (type == "error" && this.handles_error && ((status > 499 && status < 600) || status == 400)) { 
120
                arguments.ajax = this;
121
                return method(arguments)
122
            }
123

    
124
            var args = wrap.apply(this, arguments);
125
            args = _.toArray(args);
126
            var ajax_options = this;
127
            
128
            try {
129
                if (args[1] === "abort") {
130
                    api.trigger("abort");
131
                    return;
132
                }
133
            } catch(error) {
134
                console.error("error aborting", error);
135
            }
136

    
137
            // FIXME: is this good practice ??
138
            // fetch callbacks wont get called
139
            try {
140
                // identify xhr object
141
                var xhr = args[2];
142
                if (args.length == 2) {
143
                    xhr = args[0];
144
                }
145

    
146
                // do not call success for 304 responses
147
                if (args[1] === "notmodified" || xhr.status == 0 && $.browser.opera) {
148
                    if (args[2]) {
149
                        addApiCallDate(this.url, new Date(args[2].getResponseHeader('Date')), ajax_options.type);
150
                    }
151
                    return;
152
                }
153
                
154
            } catch (err) {
155
                console.error(err);
156
            }
157
            method.apply(this, args);
158
        }
159
    }
160

    
161
    api.successHandler = function(data, status, xhr) {
162
        //debug("ajax success", arguments)
163
        // on success, update the last date we called the api url
164
        addApiCallDate(this.url, new Date(xhr.getResponseHeader('Date')), this.type);
165
        return [data, status, xhr];
166
    }
167

    
168
    api.errorHandler = function(event, xhr, settings, error) {
169
        //debug("ajax error", arguments, this);
170
        arguments.ajax = this;
171
        
172
        // skip aborts
173
        if (xhr != "abort") {
174
            arguments.ajax.critical = arguments.ajax.critical == undefined ? true : arguments.ajax.critical;
175
            if (!settings.handles_error) api.trigger("error", arguments);
176
        }
177
        return arguments;
178
    }
179

    
180
    api.completeHandler = function(xhr, status) {
181
        //debug("ajax complete", arguments)
182
        return arguments;
183
    }
184

    
185
    api.beforeSendHandler = function(xhr, settings) {
186
        //debug("ajax beforeSend", arguments)
187
        // ajax settings
188
        var ajax_settings = this;
189
        return arguments;
190
    }
191

    
192

    
193
    api.call = function(url, method, data, complete, error, success) {
194
            var self = this;
195
            error = error || function(){};
196
            success = success || function(){};
197
            complete = complete || function(){};
198
            var extra = data ? data._options || {} : {};
199
            if (data && data._options) { delete data['_options'] };
200

    
201
            var params = {
202
                url: snf.config.api_url + "/" + url,
203
                data: data,
204
                success: success,
205
                complete: function() { api.trigger("call"); complete(this) },
206
                error: error
207
            }
208

    
209
            params = _.extend(params, extra);
210
            this.sync(method, this, params);
211
        },
212

    
213
    _.extend(api, bb.Events);
214

    
215
    
216
    api.updateHandler = function(options) {
217
        this.cb = options.callback;
218
        this.limit = options.limit;
219
        this.timeout = options.timeout;
220

    
221
        this.normal_timeout = options.timeout;
222
        this.fast_timeout = options.fast;
223

    
224
        this._called = 0;
225
        this.interval = undefined;
226
        this.call_on_start = options.call_on_start || true;
227

    
228
        this.running = false;
229
        
230
        // wrapper
231
        function _cb() {
232
            if (this.fast_timeout == this.timeout){
233
                this._called++;
234
            }
235

    
236
            if (this._called >= this.limit && this.fast_timeout == this.timeout) {
237
                this.timeout = this.normal_timeout;
238
                this.setInterval()
239
            }
240
            this.cb();
241
        };
242

    
243
        _cb = _.bind(_cb, this);
244

    
245
        this.faster = function() {
246
            this.timeout = this.fast_timeout;
247
            this._called = 0;
248
            this.setInterval();
249
        }
250

    
251
        this.setInterval = function() {
252
            this.trigger("clear");
253
            window.clearInterval(this.interval);
254
            this.interval = window.setInterval(_cb, this.timeout);
255
            this.running = true;
256
            if (this.call_on_start) {
257
                _cb();
258
            }
259
        }
260

    
261
        this.start = function () {
262
            this.setInterval();
263
        }
264

    
265
        this.stop = function() {
266
            this.trigger("clear");
267
            window.clearInterval(this.interval);
268
            this.running = false;
269
        }
270
    }
271
    
272
    api.stop_calls = false;
273

    
274
    // make it eventable
275
    _.extend(api.updateHandler.prototype, bb.Events);
276
    
277
})(this);