Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / ui / web / ui_model_views.js @ 00a67605

History | View | Annotate | Download (14.7 kB)

1
// Copyright 2011 GRNET S.A. All rights reserved.
2
// 
3
// Redistribution and use in source and binary forms, with or
4
// without modification, are permitted provided that the following
5
// conditions are met:
6
// 
7
//   1. Redistributions of source code must retain the above
8
//      copyright notice, this list of conditions and the following
9
//      disclaimer.
10
// 
11
//   2. Redistributions in binary form must reproduce the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer in the documentation and/or other materials
14
//      provided with the distribution.
15
// 
16
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
// POSSIBILITY OF SUCH DAMAGE.
28
// 
29
// The views and conclusions contained in the software and
30
// documentation are those of the authors and should not be
31
// interpreted as representing official policies, either expressed
32
// or implied, of GRNET S.A.
33
// 
34

    
35
;(function(root){
36

    
37
    // root
38
    var root = root;
39
    
40
    // setup namepsaces
41
    var snf = root.synnefo = root.synnefo || {};
42
    var api = snf.api = snf.api || {};
43
    var models = snf.models = snf.models || {}
44
    var storage = snf.storage = snf.storage || {};
45
    var ui = snf.ui = snf.ui || {};
46
    var util = snf.util = snf.util || {};
47

    
48
    var views = snf.views = snf.views || {}
49

    
50
    // shortcuts
51
    var bb = root.Backbone;
52
    
53
    // Reusable collection view
54
    // Helpers handling list/edit functionality of a specific Collection object
55
    views.CollectionView = views.View.extend({
56

    
57
        collection: undefined,
58
        
59
        id_tpl: 'model-item-{0}',
60
        list_tpl: '#model-item-tpl',
61
        
62
        force_reset_before_fetch: true,
63
        auto_append_actions: true,
64
        fetch_params: {},
65

    
66
        initialize: function(options) {
67
            views.CollectionView.__super__.initialize.apply(this, arguments);
68
            _.bindAll(this);
69

    
70
            this.loading = this.$(".loading-models");
71
            this.content = this.$(".content");
72
            this.list = this.$(".model-list .items-list");
73
            this.list_cont = this.$(".model-list");
74
            this.form = this.$(".model-form-cont");
75
            this.empty_msg = this.$(".items-empty-msg");
76
            
77
            this.init_collection_handlers();
78
            this.init_handlers();
79
            this.close_form();
80
            this.content.hide();
81
            this.loading.show();
82
            this.update_models();
83
            this.items = [];
84
            this.create_disabled = false;
85
            this.submiting = false;
86
        },
87
        
88
        update_models: function() {
89
            var params = {};
90
            _.extend(params, this.fetch_params);
91
            params['success'] = this.handle_reset;
92
            this.collection.fetch(params);
93
        },
94
        
95
        init_handlers: function() {
96
            this.$(".add-new").click(_.bind(this.show_form, this, undefined));
97
            
98
            this.form.find(".form-action.cancel").click(this.close_form);
99
            this.form.find(".form-action.submit").click(this.submit_form);
100
            
101
            var self = this;
102
            this.form.find("form").submit(function(e){
103
                e.preventDefault();
104
                self.submit_form();
105
                return false;
106
            });
107

    
108
            this.$(".quick-add").click(_.bind(this.show_form, this, undefined));
109
        },
110

    
111
        init_collection_handlers: function() {
112
            this.collection.bind("reset", this.handle_reset);
113
        },
114

    
115
        reset_handlers: function() {
116
            this.collection.unbind("reset", this.handle_reset);
117
        },
118
            
119
        set_items: function(models) {
120
            this.items = _.map(models, function(m) { return m.id });
121
        },
122

    
123
        _get_models: function() {
124
            return this.collection.models;
125
        },
126

    
127
        handle_reset: function(collection, models) {
128
            this.loading.hide();
129
            this.content.show();
130

    
131
            if (this.force_reset_before_update) {
132
                this.reset_list();
133
            }
134

    
135
            this.update_list(this._get_models());
136
            this.update_removed(this.items, this._get_models());
137

    
138
            this.set_items(this._get_models());
139
        },
140

    
141
        show_form: function(model) {
142
            if (this.create_disabled) { return };
143
            var create = (model === undefined || model.id === undefined ? true : false);
144
        
145
            if (create) {
146
                this.form.find(".new-title").show();
147
                this.form.find(".edit-title").hide();
148
            } else {
149
                this.form.find(".new-title").hide();
150
                this.form.find(".edit-title").show();
151
            }
152

    
153
            var model = model || new this.collection.model();
154
            this.list_cont.hide();
155

    
156
            this.reset_form(model);
157
            if (!snf.util.canReadFile()) {
158
                this.form.find(".fromfile-field").hide();
159
            }
160
            this.form.show();
161

    
162
            this.editing = true;
163
            this.editing_id = model.id;
164
            this.creating = create ? true : false;
165

    
166
            $(this.form.find("input").get(0)).focus();
167
            this.reset_form_errors();
168
        },
169

    
170
        reset_form: function(model) {
171
            if (!model.id) {
172
                this.form.find("input, textarea").val("");
173
                this.form.find("select").each(function() {
174
                    $(this).get(0).selectedIndex = 0;
175
                });
176
                return;
177
            }
178

    
179
            this.update_form_from_model(model);
180
        },
181
        
182
        show_list_msg: function(type, msg) {
183
            this.list_messages = this.list_messages || this.$(".list-messages");
184
            var el = $('<div class="{0}">{1}</div>'.format(type, msg));
185
            this.list_messages.append(el);
186
            window.setTimeout(function(){
187
                el.fadeOut(300).delay(300).remove();
188
            }, this.message_timeout || 4000)
189
        },
190
        
191
        get_fields_map: function() {
192
            return {};
193
        },
194
        
195
        reset_form_errors: function() {
196
            this.form.find(".form-field").removeClass("error");
197
            this.form.find(".form-field .errors").empty();
198
            this.form.find(".form-messages").empty();
199
        },
200

    
201
        show_form_errors: function(errors) {
202
            this.reset_form_errors();
203
            var fields_map = this.get_fields_map();
204
            this.form_messages = this.form_messages || this.$(".form-messages");
205
            
206
            _.each(errors.errors, _.bind(function(error, key){
207
                var field = this.form.find(fields_map[key]).closest(".form-field");
208
                field.addClass("error");
209
                _.each(error, function(error_msg) {
210
                    var error_el = $('<div class="error">{0}</div>'.format(error_msg));
211
                    field.find(".errors").append(error_el);
212
                });
213
            }, this));
214
            
215
            var msg = errors[''];
216
            if (msg) {
217
                var el = $('<div class="error">{0}</div>'.format(msg));
218
                this.$(".form-messages").append(el);
219
            }
220
        },
221

    
222
        clean_form_errors: function() {
223

    
224
        },
225

    
226
        get_save_params: function(data, options) {
227
            return options;
228
        },
229
        
230
        submit_form: function() {
231
            if (this.submiting) { return };
232
            var errlist = this.validate_data(this.get_form_data());
233
            if (errlist.empty()) {
234
                this.save_model(this.get_form_data());
235
            } else {
236
                this.show_form_errors(errlist);
237
            }
238
        },
239

    
240
        close_form: function() {
241
            this.editing = false;
242
            this.editing_id = undefined;
243
            this.creating = false;
244

    
245
            this.form.hide();
246
            this.list_cont.show();
247
            this.list_cont.find("h3").show();
248
        },
249

    
250
        create_model_element: function(model) {
251
            var el = this.$(this.list_tpl).clone();
252

    
253
            el.removeClass("hidden");
254
            el.addClass("model-item");
255
            el.attr("id", "item-content-" + this.id_tpl.format(model.id));
256

    
257
            if (this.auto_append_actions) {
258
                this.append_actions(el, model);
259
            }
260

    
261
            return el;
262
        },
263

    
264
        append_actions: function(el, model) {
265
            var actions = $('<div class="item-actions">' +
266
                            '<div class="item-action remove">remove</div>' + 
267
                            '<div class="item-action confirm-remove confirm">' +
268
                            '<span class="text">confirm</span>' + 
269
                            '<span class="cancel-remove cancel">cancel</span></div>' + 
270
                            '<div class="item-action edit">edit</div>' +
271
                            '</div>');
272
            el.append(actions);
273
        },
274

    
275
        bind_list_item_actions: function(el, model) {
276
            el.find(".item-actions .edit").click(_.bind(this.show_form, this, model));
277
            el.find(".item-actions .remove").click(_.bind(this.show_confirm_remove, this, el, model));
278
            el.find(".item-actions .confirm-remove .do-confirm").click(_.bind(this.delete_model, this, model)).hide();
279
            el.find(".item-actions .confirm-remove .cancel-remove").click(_.bind(this.cancel_confirm_remove, 
280
                                                                        this, el, model)).hide();
281
        },
282

    
283
        show_confirm_remove: function(el, model) {
284
            var confirmed = confirm(this.confirm_delete_msg || "Are you sure you want to delete this entry ?");
285
            if (confirmed) {
286
                this.delete_model(model);
287
            }
288
            //el.closest(".model-item").addClass("pending-delete");
289
        },
290

    
291
        cancel_confirm_remove: function(el, model) {
292
            el.closest(".model-item").removeClass("pending-delete");
293
        },
294
      
295
        _list_el: "<li></li>",
296

    
297
        new_list_el: function(model) {
298
            var list_el = $(this._list_el);
299
            el = this.create_model_element(model);
300
            list_el.attr("id", this.id_tpl.format(model.id));
301
            list_el.addClass("model-item");
302
            this.update_list_item(el, model, true);
303
            list_el.append(el);
304
            this.bind_list_item_actions(list_el, model);
305
            return list_el;
306
        },
307

    
308
        item_el: function(id) {
309
            return this.$("#" + this.id_tpl.format(id));
310
        },
311

    
312
        item_exists: function(model) {
313
            return this.item_el(model.id).length > 0;
314
        },
315

    
316
        reset_list: function() {
317
            this.list.find("model-item").remove();
318
        },
319
        
320
        save_model: function(data) {
321
            this.form.find("form-action.submit").addClass("in-progress");
322
            this.submiting = true;
323
            var created = this.creating;
324
            var options = {
325
                success: _.bind(function(){
326
                    this.update_models();
327
                    this.close_form();
328
                    if (created) {
329
                        this.show_list_msg("success", this.create_success_msg || "Entry created");
330
                    } else {
331
                        this.show_list_msg("success", this.update_success_msg || "Entry updated");
332
                    }
333
                }, this),
334

    
335
                error: _.bind(function(data, xhr){
336
                    var resp_error = "";
337
                    // try to parse response
338
                    try {
339
                        json_resp = JSON.parse(xhr.responseText);
340
                        resp_error = json_resp.errors[json_resp.non_field_key].join("<br />");
341
                    } catch (err) {}
342

    
343
                    var form_error = resp_error != "" ? 
344
                                this.create_failed_msg + " ({0})".format(resp_error) : 
345
                                this.create_failed_msg;
346
                    this.show_form_errors({'': form_error || this.submit_failed_msg || 'Entry submition failed'})
347
                }, this),
348

    
349
                complete: _.bind(function(){
350
                    this.submiting = false;
351
                    this.form.find("form-action.submit").addClass("in-progress");
352
                }, this),
353

    
354
                skip_api_error: true
355
            }
356

    
357
            if (this.editing_id && this.collection.get(this.editing_id)) {
358
                var model = this.collection.get(this.editing_id);
359
                model.save(data, this.get_save_params(data, options));
360
            } else {
361
                this.collection.create(data, this.get_save_params(data, options));
362
            }
363
        },
364

    
365
        delete_model: function(model) {
366
            this.item_el(model.id).addClass("in-progress").addClass("deleting");
367
            var self = this;
368
            model.destroy({success:this.update_models, error: function() {
369
                    self.show_list_msg("error", "Remove failed");
370
                    self.item_el(model.id).removeClass("in-progress").removeClass("deleting");
371
                }});
372
        },
373
        
374
        update_removed: function(ids, models) {
375
            var newids = _.map(models, function(m) { return m.id });
376
            _.each(_.difference(ids, newids), _.bind(function(id){
377
                this.item_el(id).remove();
378
            }, this));
379
        },
380

    
381
        update_list: function(models, reset) {
382
            var reset = reset || false;
383
            if (reset) { this.reset_list() };
384
            
385
            // handle removed items
386
            _.each(models, _.bind(function(model) {
387
                if (this.item_exists(model)) { 
388
                    this.update_list_item(this.item_el(model.id), model);
389
                    return;
390
                };
391
                var item_el = this.new_list_el(model);
392
                this.list.prepend(item_el);
393
                this.trigger("item:add", this.list, item_el, model);
394
            }, this));
395

    
396
            this.check_empty();
397
        },
398

    
399
        check_empty: function() {
400
            if (this.collection.length == 0) {
401
                this.empty_msg.show();
402
                this.list.find(".header").hide();
403
                this.el.addClass("empty");
404
            } else {
405
                this.empty_msg.hide();
406
                this.list.find(".header").show();
407
                this.el.removeClass("empty");
408
            }
409
        },
410

    
411
        reset: function (){}
412

    
413
    });
414
})(this);