Statistics
| Branch: | Tag: | Revision:

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

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
          this.handle_create_click();
229
        }, this));
230
        
231
        if (this.quota_key && !this.quota) {
232
          this.quota = synnefo.storage.quotas.get(this.quota_key);
233
        }
234

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

    
256
      post_destroy: function() {
257
        this.quota && this.quota.decrease();
258
      },
259

    
260
      handle_create_click: function() {
261
        if (this.create_button.hasClass("disabled")) { return }
262

    
263
        if (this._create_view) {
264
          this._create_view.show();
265
        }
266
      },
267

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

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

    
293
      handle_collection_change: function() {
294
        this.update_models();
295
      },
296

    
297
      handle_model_add: function(model, collection, options) {
298
        this.add_model(model);
299
        $(window).trigger("resize");
300
      },
301

    
302
      handle_model_remove: function(model, collection, options) {
303
        this.remove_model(model);
304
      },
305
      
306
      show_empty: function() {
307
        this.empty_el.show();
308
      },
309

    
310
      hide_empty: function() {
311
        this.empty_el.hide();
312
      },
313

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

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

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

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

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

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

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

    
419
      post_remove_model_view: function() {},
420

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

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

    
463
      _set_confirm: function(action) {
464
        this.pending_action = action;
465
        this.set_action_indicator(action);
466
      },
467

    
468
      _unset_confirm: function(action) {
469
        this.pending_action = undefined;
470
        this.reset_action_indicator(action);
471
      },
472

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

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

    
489
      set_confirm: function() {},
490
      unset_confirm: function() {},
491

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

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

    
538
      post_init_element: function() {},
539

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

    
547
      update_layout: function() {}
548

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

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

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

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

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

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

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

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

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

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

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

    
668
    
669
    views.ext.ModelCreateView = views.ext.ModelView.extend({});
670
    views.ext.ModelEditView = views.ext.ModelCreateView.extend({});
671

    
672
})(this);