Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (19.5 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
      action_cls_map: {
457
        'remove': 'destroy'
458
      },
459

    
460
      _set_confirm: function(action) {
461
        this.pending_action = action;
462
        this.set_action_indicator(action);
463
      },
464

    
465
      _unset_confirm: function(action) {
466
        this.pending_action = undefined;
467
        this.reset_action_indicator(action);
468
      },
469

    
470
      set_action_indicator: function(action) {
471
        action = this.action_cls_map[action] || action;
472
        var indicator = this.el.find(".action-indicator");
473
        indicator = $(indicator[indicator.length - 1]);
474
        indicator.attr("class", "").addClass("state action-indicator " + action);
475
      },
476

    
477
      reset_action_indicator: function() {
478
        var indicator = this.el.find(".action-indicator");
479
        indicator = $(indicator[indicator.length - 1]);
480
        indicator.attr("class", "").addClass("state action-indicator");
481
        if (this.pending_action) {
482
          this.set_action_indicator(this.pending_action);
483
        }
484
      },
485

    
486
      set_confirm: function() {},
487
      unset_confirm: function() {},
488

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

    
528
      get_rivet_object: function() {
529
        var model = {
530
          model: this.model
531
        }
532
        return model
533
      },
534

    
535
      post_init_element: function() {},
536

    
537
      init_element: function() {
538
        this.el.attr("id", "model-" + this.model.id);
539
        this.post_init_element();
540
        this.update_layout();
541
        return this.el;
542
      },
543

    
544
      update_layout: function() {}
545

    
546
    });
547
    
548
    views.ModelRenameView = views.ext.ModelView.extend({
549
      tpl: '#rename-view-tpl',
550
      title_attr: 'name',
551

    
552
      init: function() {
553
        views.ModelRenameView.__super__.init.apply(this, arguments);
554
        this.name_cont = this.$(".model-name");
555
        this.edit_cont = this.$(".edit");
556

    
557
        this.edit_btn = this.$(".edit-btn");
558
        this.value = this.$(".value");
559
        this.input = this.$("input");
560
        this.confirm = this.edit_cont.find(".confirm");
561
        this.cancel = this.edit_cont.find(".cancel");
562
        
563
        if (this.model.get('rename_disabled')) {
564
          this.edit_btn.remove();
565
        }
566

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

    
584
      set_edit: function() {
585
        if (this.model.get('rename_disabled')) { return }
586
        var self = this;
587
        this.input.val(this.model.get('name'));
588
        window.setTimeout(function() {
589
          self.input.focus();
590
        }, 20);
591
        this.name_cont.hide();
592
        this.edit_cont.show();
593
      },
594

    
595
      unset_edit: function() {
596
        this.name_cont.show();
597
        this.edit_cont.hide();
598
      },
599

    
600
      rename: function() {
601
        var value = _.trim(this.input.val());
602
        if (value) {
603
          this.model.rename(value);
604
          this.unset_edit();
605
        }
606
      }
607
    });
608

    
609
    views.ext.SelectModelView = views.ext.ModelView.extend({
610
      select: function() {
611
        if (!this.delegate_checked) {
612
          this.input.attr("checked", true);
613
          this.item.addClass("selected");
614
        }
615
        this.selected = true;
616
        this.trigger("change:select", this, this.selected);
617
      },
618

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

    
636
      post_init_element: function() {
637
        this.input = $(this.$("input").get(0));
638
        this.item = $(this.$(".select-item").get(0));
639
        this.delegate_checked = this.model.get('noselect');
640
        this.deselect();
641

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

    
665
    
666
    views.ext.ModelCreateView = views.ext.ModelView.extend({});
667
    views.ext.ModelEditView = views.ext.ModelCreateView.extend({});
668

    
669
})(this);