Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / views_ext.js @ 488209a5

History | View | Annotate | Download (17.9 kB)

1
;(function(root){
2
    
3
    // root
4
    var root = root;
5
    
6
    // setup namepsaces
7
    var snf = root.synnefo = root.synnefo || {};
8
    var models = snf.models = snf.models || {}
9
    var storage = snf.storage = snf.storage || {};
10
    var ui = snf.ui = snf.ui || {};
11
    var util = snf.util || {};
12
    var views = snf.views = snf.views || {}
13

    
14
    // shortcuts
15
    var bb = root.Backbone;
16
    
17
    // logging
18
    var logger = new snf.logging.logger("SNF-VIEWS");
19
    var debug = _.bind(logger.debug, logger);
20

    
21
    // Extended views module
22
    // View objects to provide more sophisticated base objects for views 
23
    // that are bind to existing storage model/collection objects.
24
    views.ext = {};
25
    
26
    views.ext.View = views.View.extend({
27
      rivets_view: false,
28
      rivets: undefined,
29
      container: undefined,
30
      classes:'',
31

    
32
      storage_handlers: {},
33

    
34
      init: function() {},
35
      post_init: function() {},
36

    
37
      initialize: function(options) {
38
        views.ext.View.__super__.initialize.apply(this, arguments);
39
        this.container = options && options.container;
40
        this._subviews = [];
41
        if (this.tpl) {
42
          this.el = $(this.tpl).clone().removeClass("hidden").removeAttr('id');
43
        }
44
        this.init.apply(this, arguments);
45
        this.post_init.apply(this, arguments);
46
        this.append_to_container();
47
        $(this.el).addClass(this.classes);
48
        _.bindAll(this);
49
      },
50
      
51
      append_to_container: function() {
52
        if (!this.container) { return }
53
        var cont = $(this.container);
54
        cont.append(this.el);
55
      },
56

    
57
      create_view: function(view_cls, options) {
58
        var options = _.extend({}, options);
59
        options.parent_view = this;
60
        var view = new view_cls(options);
61
        return view;
62
      },
63

    
64
      add_subview: function(view) {
65
        view.parent_view = this;
66
        this._subviews.push(view);
67
      },
68

    
69
      remove_view: function(view) {
70
        this._subviews = _.without(this._subviews, view);
71
      },
72
      
73
      hide_subviews: function() {
74
        _.each(this._subviews, function(view) { 
75
          view.hide(true); 
76
        });
77
      },
78

    
79
      show_subviews: function() {
80
        _.each(this._subviews, function(view) { 
81
          view.show(true); 
82
        });
83
      },
84
      
85
      pre_hide: function() {
86
        this.rivets_unbind();
87
        this.remove_handlers();
88
      },
89
      
90
      get_extra_rivet_models: function() {},
91

    
92
      get_rivet_object: function() {
93
        return this.rivet_object;
94
      },
95

    
96
      post_hide: function() {
97
        this.hide_subviews();
98
        this.trigger("hide");
99
      },
100
      
101
      rivets_init: function() {
102
        if (!this.rivets_view) { return }
103
        var rivet_object = this.get_rivet_object();
104
        rivet_object['view'] = this;
105
        if (this.el != $("body").get(0)) {
106
          this.rivets = rivets.bind(this.el, rivet_object);
107
        } else {
108
        }
109
      },
110
      
111
      rivets_update: function() {
112
        if (!this.rivets_view) { return }
113
        this.rivets.update();
114
      },
115

    
116
      rivets_bind: function() {
117
        if (!this.rivets_view) { return }
118
        if (!this.rivets) { this.rivets_init(); return }
119
        var rivet_object = this.get_rivet_object();
120
        rivet_object['view'] = this;
121
        this.rivets.models = rivet_object;
122
        //this.rivets.build();
123
        this.rivets.bind();
124
      },
125

    
126
      rivets_unbind: function() {
127
        if (!this.rivets_view) { return }
128
        if (!this.rivets) { return }
129
        this.rivets.unbind();
130
      },
131

    
132
      pre_show: function() {
133
        this.set_handlers();
134
        this.rivets_bind();
135
        this.show_subviews();
136
      },
137
      
138
      resolve_storage_object: function(id) {
139
        var result;
140
        if (this['resolve_' + id + '_storage_object']) {
141
          return this['resolve_' + id + '_storage_object']();
142
        }
143
        result = synnefo.storage[id];
144
        return result ? result : this.collection
145
      },
146
      
147
      each_storage_handler: function(cb, context) {
148
        if (!context) { context = this }
149
        _.each(this.storage_handlers, function(handlers, object_name) {
150
          _.each(handlers, function(events, handler_name) {
151
            _.each(events, function(event) {
152
              object = this.resolve_storage_object(object_name);
153
              handler = this['handle_' + handler_name];
154
              if (!handler) {
155
                throw "Handler " + handler_name + " does not exist";
156
              }
157
              if (!object) {
158
                throw "Storage object " + object_name + " does not exist";
159
              }
160
              cb.call(context, object, event, handler);
161
            }, this);
162
          }, this);
163
        }, this);
164
      },
165
      
166
      get_handler: function(id) {
167
      },
168

    
169
      set_handlers: function() {
170
        this.each_storage_handler(this.set_handler, this);
171
      },
172

    
173
      remove_handlers: function() {
174
        this.each_storage_handler(this.remove_handler, this);
175
      },
176
      
177
      set_handler: function(object, event, handler) {
178
        object.bind(event, handler);
179
      },
180

    
181
      remove_handler: function(object, event, handler) {
182
        object.unbind(event, handler);
183
      }
184
    });
185

    
186
    views.ext.PaneView = views.ext.View.extend({
187
      collection_view_cls: null,
188
      collection_view_selector: '.collection',
189
      init: function() {
190
        var options = {};
191
        options['el'] = $(this.$(this.collection_view_selector).get(0));
192
        this.collection_view = this.create_view(this.collection_view_cls, options);
193
        this.add_subview(this.collection_view);
194
      },
195
    });
196

    
197
    views.ext.CollectionView = views.ext.View.extend({
198
      collection: undefined,
199
      model_view_cls: undefined,
200
      animation_speed: 200,
201
      quota_key: undefined,
202
      quota_limit_message: undefined,
203

    
204
      init: function() {
205
        var handlers = {};
206
        handlers[this.collection_name] = {
207
          'collection_change': ['update', 'sort'],
208
          'collection_reset': ['reset'],
209
          'model_change': ['change'],
210
          'model_add': ['add'],
211
          'model_remove': ['remove']
212
        }
213
        this.storage_handlers = _.extend(handlers, this.storage_handlers)
214
        this._model_views = {};
215
        this.list_el = $(this.$(".items-list").get(0));
216
        this.empty_el = $(this.$(".empty-list").get(0));
217
        if (this.create_view_cls) {
218
          this._create_view = new this.create_view_cls();
219
          this._create_view.parent_view = this;
220
        }
221

    
222
        this.create_button = this.$(".create-button a");
223
        this.create_button.click(_.bind(function(e) {
224
          e.preventDefault();
225
          this.handle_create_click();
226
        }, this));
227
        
228
        if (this.quota_key && !this.quota) {
229
          this.quota = synnefo.storage.quotas.get(this.quota_key);
230
        }
231

    
232
        if (this.quota) {
233
          this.quota.bind("change", _.bind(this.update_quota, this));
234
          this.update_quota();
235
        }
236
      },
237
      
238
      update_quota: function() {
239
        var available = this.quota.get_available();
240
        if (available > 0) {
241
          this.create_button.removeClass("disabled");
242
          this.create_button.attr("title", this.quota_limit_message || "Quota limit reached")
243
        } else {
244
          this.create_button.addClass("disabled");
245
          this.create_button.attr("title", "");
246
        }
247
      },
248
      
249
      post_create: function() {
250
        this.quota && this.quota.increase();
251
      },
252

    
253
      post_destroy: function() {
254
        this.quota && this.quota.decrease();
255
      },
256

    
257
      handle_create_click: function() {
258
        if (this.create_button.hasClass("disabled")) { return }
259

    
260
        if (this._create_view) {
261
          this._create_view.show();
262
        }
263
      },
264

    
265
      pre_show: function() {
266
        views.ext.CollectionView.__super__.pre_show.apply(this, arguments);
267
        this.update_models();
268
      },
269
      
270
      handle_collection_reset: function() {
271
        this.update_models();
272
      },
273

    
274
      handle_model_change: function(model) {
275
        var el, index, model, parent, view, anim;
276
        view = this._model_views[model.id];
277
        if (!view) { return }
278
        el = view.el;
279
        parent = this.parent_for_model(model);
280
        index = this.collection.indexOf(model);
281
        if (!parent.find(el).length) {
282
          anim = true;
283
          this.place_in_parent(parent, el, model, index, anim);
284
        }
285
        if (index != view.el.data('index')) {
286
          this.place_in_parent(parent, el, model, index, false);
287
        }
288
      },
289

    
290
      handle_collection_change: function() {
291
        this.update_models();
292
      },
293

    
294
      handle_model_add: function(model, collection, options) {
295
        this.add_model(model);
296
        $(window).trigger("resize");
297
      },
298

    
299
      handle_model_remove: function(model, collection, options) {
300
        this.remove_model(model);
301
      },
302
      
303
      show_empty: function() {
304
        this.empty_el.show();
305
      },
306

    
307
      hide_empty: function() {
308
        this.empty_el.hide();
309
      },
310

    
311
      check_empty: function() {
312
        if (this.collection.length == 0) {
313
          this.show_empty();
314
          this.list_el.hide();
315
        } else {
316
          this.list_el.show();
317
          this.hide_empty();
318
        }
319
      },
320
      
321
      parent_for_model: function(model) {
322
        return this.list_el;
323
      },
324
      
325
      place_in_parent: function(parent, el, m, index, anim) {
326
        var place_func, place_func_context, position_found, exists;
327

    
328
        _.each(parent.find(".model-item"), function(el) {
329
          var el = $(el);
330
          var el_index = el.data('index');
331
          if (!el_index || position_found) { return };
332
          if (parseInt(el_index) < index) {
333
            place_func = el.before;
334
            place_func_context = el;
335
            position_found = true;
336
          }
337
        });
338
        
339
        if (!position_found) {
340
          place_func = parent.append;
341
          place_func_context = parent;
342
        }
343

    
344
        if (anim) {
345
          var self = this;
346
          el.fadeOut(this.animation_speed, function() {
347
            place_func.call(place_func_context, el);
348
            el.fadeIn(self.animation_speed);
349
          });
350
        } else {
351
          place_func.call(place_func_context, el);
352
        }
353
        el.attr("data-index", index);
354
      },
355
      
356
      get_model_view_cls: function(m) {
357
        return this.model_view_cls
358
      },
359

    
360
      add_model: function(m, index) {
361
        // if no available class for model exists, skip model add
362
        var view_cls = this.get_model_view_cls(m);
363
        if (!view_cls) { return }
364
        
365
        // avoid duplicate entries
366
        if (this._model_views[m.id]) { return }
367
        
368
        // handle empty collection
369
        this.check_empty();
370
        
371
        // initialize view
372
        var view = this.create_view(this.get_model_view_cls(m), {model: m});
373
        this.add_model_view(view, m, index);
374
      },
375

    
376
      add_model_view: function(view, model, index) {
377
        // append html element to the parent
378
        var el = view.init_element();
379
        // append to registry object
380
        this._model_views[model.id] = view;
381
        el.addClass("model-item");
382
        // where to place ?
383
        var parent = this.parent_for_model(model);
384
        // append
385
        this.place_in_parent(parent, el, model, index);
386
        // make it visible by default
387
        this.add_subview(view);
388
        view.show(true);
389
        this.post_add_model_view(view, model);
390
      },
391
      post_add_model_view: function() {},
392

    
393
      each_model_view: function(cb, context) {
394
        if (!context) { context = this };
395
        _.each(this._model_views, function(view, model_id){
396
          var model = this.collection.get(model_id);
397
          cb.call(this, model, view, model_id);
398
        }, this);
399
      },
400

    
401
      remove_model: function(m) {
402
        var model_view = this._model_views[m.id];
403
        if (!model_view) {
404
          console.error("no view found");
405
          return;
406
        }
407
        model_view.hide();
408
        model_view.el.remove();
409
        this.remove_view(model_view);
410
        this.post_remove_model_view(model_view, m);
411
        $(window).trigger("resize");
412
        delete this._model_views[m.id];
413
        this.check_empty();
414
      },
415

    
416
      post_remove_model_view: function() {},
417

    
418
      update_models: function(m) {
419
        this.check_empty();
420
        this.collection.each(function(model, index) {
421
          if (!(model.id in this._model_views)) {
422
            this.add_model(model, index);
423
          } else {
424
            if (model != this._model_views[model.id].model) {
425
              this._model_views[model.id].model = model;
426
              this._model_views[model.id].rivets_unbind();
427
              this._model_views[model.id].rivets_bind();
428
            }
429
            this.handle_model_change(model);
430
          }
431
        }, this);
432
        
433
        this.each_model_view(function(model, view, model_id){
434
          if (!model) {
435
            model = {'id': model_id};
436
            this.remove_model(model);
437
          }
438
        })
439
      }
440
    });
441

    
442
    views.ext.ModelView = views.ext.View.extend({
443
      rivets_view: true,
444
      
445
      initialize: function() {
446
        views.ext.ModelView.__super__.initialize.apply(this, arguments);
447
        var actions = this.model.get('actions');
448
        if (actions) {
449
          this.init_action_methods(this.model.get('actions'));
450
          this.bind("hide", function() {
451
            actions.reset_pending();
452
          });
453
        }
454
      },
455
      
456
      set_confirm: function() {},
457
      unset_confirm: function() {},
458

    
459
      init_action_methods: function(actions) {
460
        _.each(actions.actions, function(action) {
461
          var method;
462
          method = 'set_{0}_confirm'.format(action);
463
          if (this[method]) { return }
464
          this[method] = _.bind(function(model, ev) {
465
            if (ev) { ev.stopPropagation() }
466
            var data = {};
467
            this.set_confirm(action);
468
            this.model.actions.set_pending_action(action);
469
          }, this);
470
          method = 'unset_{0}_confirm'.format(action);
471
          if (this[method]) { return }
472
          this[method] = _.bind(function(model, ev) {
473
            if (ev) { ev.stopPropagation() }
474
            var data = {};
475
            this.unset_confirm(action);
476
            this.model.actions.unset_pending_action(action);
477
          }, this);
478
        }, this);
479
      },
480

    
481
      get_rivet_object: function() {
482
        var model = {
483
          model: this.model
484
        }
485
        return model
486
      },
487

    
488
      post_init_element: function() {},
489

    
490
      init_element: function() {
491
        this.el.attr("id", "model-" + this.model.id);
492
        this.post_init_element();
493
        this.update_layout();
494
        return this.el;
495
      },
496

    
497
      update_layout: function() {}
498

    
499
    });
500
    
501
    views.ModelRenameView = views.ext.ModelView.extend({
502
      tpl: '#rename-view-tpl',
503
      title_attr: 'name',
504

    
505
      init: function() {
506
        views.ModelRenameView.__super__.init.apply(this, arguments);
507
        this.name_cont = this.$(".model-name");
508
        this.edit_cont = this.$(".edit");
509

    
510
        this.edit_btn = this.$(".edit-btn");
511
        this.value = this.$(".value");
512
        this.input = this.$("input");
513
        this.confirm = this.edit_cont.find(".confirm");
514
        this.cancel = this.edit_cont.find(".cancel");
515
        
516
        if (this.model.get('rename_disabled')) {
517
          this.edit_btn.remove();
518
        }
519

    
520
        this.value.dblclick(_.bind(function(e) {
521
          this.set_edit();
522
        }, this));
523
        this.input.bind('keyup', _.bind(function(e) {
524
          // enter keypress
525
          if (e.which == 13) { this.rename(); }
526
          // esc keypress
527
          if (e.which == 27) { this.unset_edit(); }
528
        }, this));
529
        // initial state
530
        this.unset_edit();
531
      },
532
      
533
      post_hide: function() {
534
        this.unset_edit();
535
      },
536

    
537
      set_edit: function() {
538
        if (this.model.get('rename_disabled')) { return }
539
        var self = this;
540
        this.input.val(this.model.get('name'));
541
        window.setTimeout(function() {
542
          self.input.focus();
543
        }, 20);
544
        this.name_cont.hide();
545
        this.edit_cont.show();
546
      },
547

    
548
      unset_edit: function() {
549
        this.name_cont.show();
550
        this.edit_cont.hide();
551
      },
552

    
553
      rename: function() {
554
        var value = _.trim(this.input.val());
555
        if (value) {
556
          this.model.rename(value);
557
          this.unset_edit();
558
        }
559
      }
560
    });
561

    
562
    views.ext.SelectModelView = views.ext.ModelView.extend({
563
      select: function() {
564
        if (!this.delegate_checked) {
565
          this.input.attr("checked", true);
566
          this.item.addClass("selected");
567
        }
568
        this.selected = true;
569
        this.trigger("change:select", this, this.selected);
570
      },
571

    
572
      deselect: function() {
573
        if (!this.delegate_checked) {
574
          this.input.attr("checked", false);
575
          this.item.removeClass("selected");
576
        }
577
        this.selected = false;
578
        this.trigger("change:select", this, this.selected);
579
      },
580
      
581
      toggle_select: function() {
582
        if (this.selected) { 
583
          this.deselect();
584
        } else {
585
          this.select();
586
        }
587
      },
588

    
589
      post_init_element: function() {
590
        this.input = $(this.$("input").get(0));
591
        this.item = $(this.$(".select-item").get(0));
592
        this.delegate_checked = this.model.get('noselect');
593
        this.deselect();
594

    
595
        var self = this;
596
        if (self.model.get('forced')) {
597
          this.select();
598
          this.input.attr("disabled", true);
599
          $(this.el).attr('title', this.forced_title);
600
          $(this.el).tooltip({
601
            'tipClass': 'tooltip', 
602
            'position': 'top center',
603
            'offset': [29, 0]
604
          });
605
        }
606
        
607
        $(this.item).click(function(e) {
608
          if (self.model.get('forced')) { return }
609
          e.stopPropagation();
610
          self.toggle_select();
611
        });
612
        
613
        views.ext.SelectModelView.__super__.post_init_element.apply(this,
614
                                                                    arguments);
615
      }
616
    });
617

    
618
    
619
    views.ext.ModelCreateView = views.ext.ModelView.extend({});
620
    views.ext.ModelEditView = views.ext.ModelCreateView.extend({});
621

    
622
})(this);