Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (19.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 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
        if (view.css_classes) {
62
          view.el.addClass(view.css_classes)
63
        }
64
        return view;
65
      },
66

    
67
      add_subview: function(view) {
68
        view.parent_view = this;
69
        this._subviews.push(view);
70
      },
71

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

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

    
95
      get_rivet_object: function() {
96
        return this.rivet_object;
97
      },
98

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

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

    
129
      rivets_unbind: function() {
130
        if (!this.rivets_view) { return }
131
        if (!this.rivets) { return }
132
        this.rivets.unbind();
133
      },
134

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

    
172
      set_handlers: function() {
173
        this.each_storage_handler(this.set_handler, this);
174
      },
175

    
176
      remove_handlers: function() {
177
        this.each_storage_handler(this.remove_handler, this);
178
      },
179
      
180
      set_handler: function(object, event, handler) {
181
        object.bind(event, handler);
182
      },
183

    
184
      remove_handler: function(object, event, handler) {
185
        object.unbind(event, handler);
186
      }
187
    });
188

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

    
200
    views.ext.CollectionView = views.ext.View.extend({
201
      collection: undefined,
202
      model_view_cls: undefined,
203
      animation_speed: 200,
204
      quota_key: undefined,
205
      quota_limit_message: undefined,
206

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

    
225
        this.create_button = this.$(".create-button a");
226
        this.create_button.click(_.bind(function(e) {
227
          e.preventDefault();
228
          if (this.$(".create-button a").hasClass("disabled")) {
229
            return;
230
          }
231
          this.handle_create_click();
232
        }, this));
233
        
234
        if (this.quota_key && !this.quota) {
235
          this.quota = synnefo.storage.quotas.get(this.quota_key);
236
        }
237

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

    
259
      post_destroy: function() {
260
        this.quota && this.quota.decrease();
261
      },
262

    
263
      handle_create_click: function() {
264
        if (this.create_button.hasClass("disabled")) { return }
265

    
266
        if (this._create_view) {
267
          this._create_view.show();
268
        }
269
      },
270

    
271
      pre_show: function() {
272
        views.ext.CollectionView.__super__.pre_show.apply(this, arguments);
273
        this.update_models();
274
      },
275
      
276
      handle_collection_reset: function() {
277
        this.update_models();
278
      },
279

    
280
      handle_model_change: function(model) {
281
        var el, index, model, parent, view, anim;
282
        view = this._model_views[model.id];
283
        if (!view) { return }
284
        el = view.el;
285
        parent = this.parent_for_model(model);
286
        index = this.collection.indexOf(model);
287
        if (!parent.find(el).length) {
288
          anim = true;
289
          this.place_in_parent(parent, el, model, index, anim);
290
        }
291
        if (index != view.el.data('index')) {
292
          this.place_in_parent(parent, el, model, index, false);
293
        }
294
      },
295

    
296
      handle_collection_change: function() {
297
        this.update_models();
298
      },
299

    
300
      handle_model_add: function(model, collection, options) {
301
        this.add_model(model);
302
        $(window).trigger("resize");
303
      },
304

    
305
      handle_model_remove: function(model, collection, options) {
306
        this.remove_model(model);
307
      },
308
      
309
      show_empty: function() {
310
        this.empty_el.show();
311
      },
312

    
313
      hide_empty: function() {
314
        this.empty_el.hide();
315
      },
316

    
317
      check_empty: function() {
318
        if (this.collection.length == 0) {
319
          this.show_empty();
320
          this.list_el.hide();
321
        } else {
322
          this.list_el.show();
323
          this.hide_empty();
324
        }
325
      },
326
      
327
      parent_for_model: function(model) {
328
        return this.list_el;
329
      },
330
      
331
      place_in_parent: function(parent, el, m, index, anim) {
332
        var place_func, place_func_context, position_found, exists;
333

    
334
        _.each(parent.find(">.model-item"), function(el) {
335
          var el = $(el);
336
          var el_index = el.data('index');
337
          if (!el_index || position_found) { return };
338
          if (parseInt(el_index) < index) {
339
            place_func = el.before;
340
            place_func_context = el;
341
            position_found = true;
342
          }
343
        });
344
        
345
        if (!position_found) {
346
          place_func = parent.append;
347
          place_func_context = parent;
348
        }
349

    
350
        if (anim) {
351
          var self = this;
352
          el.fadeOut(this.animation_speed, function() {
353
            place_func.call(place_func_context, el);
354
            el.fadeIn(self.animation_speed);
355
          });
356
        } else {
357
          place_func.call(place_func_context, el);
358
        }
359
        el.attr("data-index", index);
360
      },
361
      
362
      get_model_view_cls: function(m) {
363
        return this.model_view_cls
364
      },
365

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

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

    
399
      each_model_view: function(cb, context) {
400
        if (!context) { context = this };
401
        _.each(this._model_views, function(view, model_id){
402
          var model = this.collection.get(model_id);
403
          cb.call(this, model, view, model_id);
404
        }, this);
405
      },
406

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

    
422
      post_remove_model_view: function() {},
423

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

    
448
    views.ext.ModelView = views.ext.View.extend({
449
      rivets_view: true,
450
      
451
      initialize: function() {
452
        views.ext.ModelView.__super__.initialize.apply(this, arguments);
453
        var actions = this.model.get('actions');
454
        if (actions) {
455
          this.init_action_methods(this.model.get('actions'));
456
          this.bind("hide", function() {
457
            actions.reset_pending();
458
          });
459
        }
460
      },
461
      
462
      action_cls_map: {
463
        'remove': 'destroy'
464
      },
465

    
466
      _set_confirm: function(action) {
467
        this.pending_action = action;
468
        this.set_action_indicator(action);
469
      },
470

    
471
      _unset_confirm: function(action) {
472
        this.pending_action = undefined;
473
        this.reset_action_indicator(action);
474
      },
475

    
476
      set_action_indicator: function(action) {
477
        action = this.action_cls_map[action] || action;
478
        var indicator = this.el.find(".action-indicator");
479
        indicator = $(indicator[indicator.length - 1]);
480
        indicator.attr("class", "").addClass("state action-indicator " + action);
481
      },
482

    
483
      reset_action_indicator: function() {
484
        var indicator = this.el.find(".action-indicator");
485
        indicator = $(indicator[indicator.length - 1]);
486
        indicator.attr("class", "").addClass("state action-indicator");
487
        if (this.pending_action) {
488
          this.set_action_indicator(this.pending_action);
489
        }
490
      },
491

    
492
      set_confirm: function() {},
493
      unset_confirm: function() {},
494

    
495
      init_action_methods: function(actions) {
496
        var self = this;
497
        if (this.model && this.model.actions) {
498
          this.model.actions.bind("reset-pending", function() {
499
            this._unset_confirm();
500
          }, this);
501
          this.model.actions.bind("set-pending", function(action) {
502
            this._set_confirm(action)
503
          }, this);
504
        }
505
        _.each(actions.actions, function(action) {
506
          this.el.find(".action-container." + action).hover(function() {
507
            self.set_action_indicator(action);
508
          }, function() {
509
            self.reset_action_indicator();
510
          });
511
          var method;
512
          method = 'set_{0}_confirm'.format(action);
513
          if (this[method]) { return }
514
          this[method] = _.bind(function(model, ev) {
515
            if (ev) { ev.stopPropagation() }
516
            var data = {};
517
            this._set_confirm(action);
518
            this.set_confirm(action);
519
            this.model.actions.set_pending_action(action);
520
          }, this);
521
          method = 'unset_{0}_confirm'.format(action);
522
          if (this[method]) { return }
523
          this[method] = _.bind(function(model, ev) {
524
            if (ev) { ev.stopPropagation() }
525
            var data = {};
526
            this._unset_confirm(action);
527
            this.unset_confirm(action);
528
            this.model.actions.unset_pending_action(action);
529
          }, this);
530
        }, this);
531
      },
532

    
533
      get_rivet_object: function() {
534
        var model = {
535
          model: this.model
536
        }
537
        return model
538
      },
539

    
540
      post_init_element: function() {},
541

    
542
      init_element: function() {
543
        this.el.attr("id", "model-" + this.model.id);
544
        this.post_init_element();
545
        this.update_layout();
546
        return this.el;
547
      },
548

    
549
      update_layout: function() {}
550

    
551
    });
552
    
553
    views.ModelRenameView = views.ext.ModelView.extend({
554
      tpl: '#rename-view-tpl',
555
      title_attr: 'name',
556

    
557
      init: function() {
558
        views.ModelRenameView.__super__.init.apply(this, arguments);
559
        this.name_cont = this.$(".model-name");
560
        this.edit_cont = this.$(".edit");
561

    
562
        this.edit_btn = this.$(".edit-btn");
563
        this.value = this.$(".value");
564
        this.input = this.$("input");
565
        this.confirm = this.edit_cont.find(".confirm");
566
        this.cancel = this.edit_cont.find(".cancel");
567
        
568
        if (this.model.get('rename_disabled')) {
569
          this.edit_btn.remove();
570
        }
571

    
572
        this.value.dblclick(_.bind(function(e) {
573
          this.set_edit();
574
        }, this));
575
        this.input.bind('keyup', _.bind(function(e) {
576
          // enter keypress
577
          if (e.which == 13) { this.rename(); }
578
          // esc keypress
579
          if (e.which == 27) { this.unset_edit(); }
580
        }, this));
581
        // initial state
582
        this.unset_edit();
583
      },
584
      
585
      post_hide: function() {
586
        this.unset_edit();
587
      },
588

    
589
      set_edit: function() {
590
        if (this.model.get('rename_disabled')) { return }
591
        var self = this;
592
        this.input.val(this.model.get('name'));
593
        window.setTimeout(function() {
594
          self.input.focus();
595
        }, 20);
596
        this.name_cont.hide();
597
        this.edit_cont.show();
598
      },
599

    
600
      unset_edit: function() {
601
        this.name_cont.show();
602
        this.edit_cont.hide();
603
      },
604

    
605
      rename: function() {
606
        var value = _.trim(this.input.val());
607
        if (value) {
608
          this.model.rename(value);
609
          this.unset_edit();
610
        }
611
      }
612
    });
613

    
614
    views.ext.SelectModelView = views.ext.ModelView.extend({
615
      select: function() {
616
        if (!this.delegate_checked) {
617
          this.input.attr("checked", true);
618
          this.item.addClass("selected");
619
        }
620
        this.selected = true;
621
        this.trigger("change:select", this, this.selected);
622
      },
623

    
624
      deselect: function() {
625
        if (!this.delegate_checked) {
626
          this.input.attr("checked", false);
627
          this.item.removeClass("selected");
628
        }
629
        this.selected = false;
630
        this.trigger("change:select", this, this.selected);
631
      },
632
      
633
      toggle_select: function() {
634
        if (this.selected) { 
635
          this.deselect();
636
        } else {
637
          this.select();
638
        }
639
      },
640

    
641
      post_init_element: function() {
642
        this.input = $(this.$("input").get(0));
643
        this.item = $(this.$(".select-item").get(0));
644
        this.delegate_checked = this.model.get('noselect');
645
        this.deselect();
646

    
647
        var self = this;
648
        if (self.model.get('forced')) {
649
          this.select();
650
          this.input.attr("disabled", true);
651
          $(this.el).attr('title', this.forced_title);
652
          $(this.el).tooltip({
653
            'tipClass': 'tooltip', 
654
            'position': 'top center',
655
            'offset': [29, 0]
656
          });
657
        }
658
        
659
        $(this.item).click(function(e) {
660
          if (self.model.get('forced')) { return }
661
          e.stopPropagation();
662
          self.toggle_select();
663
        });
664
        
665
        views.ext.SelectModelView.__super__.post_init_element.apply(this,
666
                                                                    arguments);
667
      }
668
    });
669

    
670
    
671
    views.ext.ModelCreateView = views.ext.ModelView.extend({});
672
    views.ext.ModelEditView = views.ext.ModelCreateView.extend({});
673

    
674
})(this);