Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (16.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
        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

    
202
      init: function() {
203
        var handlers = {};
204
        handlers[this.collection_name] = {
205
          'collection_change': ['update', 'sort'],
206
          'collection_reset': ['reset'],
207
          'model_change': ['change'],
208
          'model_add': ['add'],
209
          'model_remove': ['remove']
210
        }
211
        this.storage_handlers = _.extend(handlers, this.storage_handlers)
212
        this._model_views = {};
213
        this.list_el = $(this.$(".items-list").get(0));
214
        this.empty_el = $(this.$(".empty-list").get(0));
215
        if (this.create_view_cls) {
216
          this._create_view = new this.create_view_cls();
217
        }
218
        this.$(".create-button a").click(_.bind(function(e) {
219
          e.preventDefault();
220
          this.handle_create_click();
221
        }, this));
222
      },
223
      
224
      handle_create_click: function() {
225
        if (this._create_view) {
226
          this._create_view.show();
227
        }
228
      },
229

    
230
      pre_show: function() {
231
        views.ext.CollectionView.__super__.pre_show.apply(this, arguments);
232
        this.update_models();
233
      },
234
      
235
      handle_collection_reset: function() {
236
        this.update_models();
237
      },
238

    
239
      handle_model_change: function(model) {
240
        var el, index, model, parent, view, anim;
241
        view = this._model_views[model.id];
242
        if (!view) { return }
243
        el = view.el;
244
        parent = this.parent_for_model(model);
245
        if (!parent.find(el).length) {
246
          index = this.collection.indexOf(model);
247
          anim = true;
248
          this.place_in_parent(parent, el, model, index, anim);
249
        }
250
      },
251

    
252
      handle_collection_change: function() {
253
        this.update_models();
254
      },
255

    
256
      handle_model_add: function(model, collection, options) {
257
        this.add_model(model);
258
      },
259

    
260
      handle_model_remove: function(model, collection, options) {
261
        this.remove_model(model);
262
      },
263
      
264
      show_empty: function() {
265
        this.empty_el.show();
266
      },
267

    
268
      hide_empty: function() {
269
        this.empty_el.hide();
270
      },
271

    
272
      check_empty: function() {
273
        if (this.collection.length == 0) {
274
          this.show_empty();
275
          this.list_el.hide();
276
        } else {
277
          this.list_el.show();
278
          this.hide_empty();
279
        }
280
      },
281
      
282
      parent_for_model: function(model) {
283
        return this.list_el;
284
      },
285
      
286
      place_in_parent: function(parent, el, m, index, anim) {
287
        var place_func, place_func_context, position_found;
288

    
289
        _.each(parent.find(".model-item"), function(el) {
290
          var el = $(el);
291
          var el_index = el.data('index');
292
          if (!el_index || position_found) { return };
293
          if (parseInt(el_index) < index) {
294
            place_func = el.before;
295
            place_func_context = el;
296
            position_found = true;
297
          }
298
        });
299
        
300
        if (!position_found) {
301
          place_func = parent.append;
302
          place_func_context = parent;
303
        }
304

    
305
        if (anim) {
306
          var self = this;
307
          el.fadeOut(this.animation_speed, function() {
308
            place_func.call(place_func_context, el);
309
            el.fadeIn(self.animation_speed);
310
          });
311
        } else {
312
          place_func.call(place_func_context, el);
313
        }
314
        el.attr("data-index", index);
315
      },
316
      
317
      get_model_view_cls: function(m) {
318
        return this.model_view_cls
319
      },
320

    
321
      add_model: function(m, index) {
322
        // if no available class for model exists, skip model add
323
        var view_cls = this.get_model_view_cls(m);
324
        if (!view_cls) { return }
325
        
326
        // avoid duplicate entries
327
        if (this._model_views[m.id]) { return }
328
        
329
        // handle empty collection
330
        this.check_empty();
331
        
332
        // initialize view
333
        var view = this.create_view(this.get_model_view_cls(m), {model: m});
334
        this.add_model_view(view, m, index);
335
      },
336

    
337
      add_model_view: function(view, model, index) {
338
        // append html element to the parent
339
        var el = view.init_element();
340
        // append to registry object
341
        this._model_views[model.id] = view;
342
        el.addClass("model-item");
343
        // where to place ?
344
        var parent = this.parent_for_model(model);
345
        // append
346
        this.place_in_parent(parent, el, model, index);
347
        // make it visible by default
348
        this.add_subview(view);
349
        view.show(true);
350
        this.post_add_model_view(view, model);
351
      },
352
      post_add_model_view: function() {},
353

    
354
      each_model_view: function(cb, context) {
355
        if (!context) { context = this };
356
        _.each(this._model_views, function(view, model_id){
357
          var model = this.collection.get(model_id);
358
          cb.call(this, model, view, model_id);
359
        }, this);
360
      },
361

    
362
      remove_model: function(m) {
363
        var model_view = this._model_views[m.id];
364
        if (!model_view) {
365
          console.error("no view found");
366
          return;
367
        }
368
        model_view.hide();
369
        model_view.el.remove();
370
        this.remove_view(model_view);
371
        this.post_remove_model_view(model_view, m);
372
        delete this._model_views[m.id];
373
        this.check_empty();
374
      },
375

    
376
      post_remove_model_view: function() {},
377

    
378
      update_models: function(m) {
379
        this.check_empty();
380
        this.collection.each(function(model, index) {
381
          if (!(model.id in this._model_views)) {
382
            this.add_model(model, index);
383
          } else {
384
            if (model != this._model_views[model.id].model) {
385
              this._model_views[model.id].model = model;
386
              this._model_views[model.id].rivets_unbind();
387
              this._model_views[model.id].rivets_bind();
388
            }
389
            this.handle_model_change(model);
390
          }
391
        }, this);
392
        
393
        this.each_model_view(function(model, view, model_id){
394
          if (!model) {
395
            model = {'id': model_id};
396
            this.remove_model(model);
397
          }
398
        })
399
      }
400
    });
401

    
402
    views.ext.ModelView = views.ext.View.extend({
403
      rivets_view: true,
404
      
405
      initialize: function() {
406
        views.ext.ModelView.__super__.initialize.apply(this, arguments);
407
        var actions = this.model.get('actions');
408
        if (actions) {
409
          this.init_action_methods(this.model.get('actions'));
410
          this.bind("hide", function() {
411
            actions.reset_pending();
412
          });
413
        }
414
      },
415
      
416
      set_confirm: function() {},
417
      unset_confirm: function() {},
418

    
419
      init_action_methods: function(actions) {
420
        _.each(actions.actions, function(action) {
421
          var method;
422
          method = 'set_{0}_confirm'.format(action);
423
          if (this[method]) { return }
424
          this[method] = _.bind(function(model, ev) {
425
            if (ev) { ev.stopPropagation() }
426
            var data = {};
427
            this.set_confirm(action);
428
            this.model.actions.set_pending_action(action);
429
          }, this);
430
          method = 'unset_{0}_confirm'.format(action);
431
          if (this[method]) { return }
432
          this[method] = _.bind(function(model, ev) {
433
            if (ev) { ev.stopPropagation() }
434
            var data = {};
435
            this.unset_confirm(action);
436
            this.model.actions.unset_pending_action(action);
437
          }, this);
438
        }, this);
439
      },
440

    
441
      get_rivet_object: function() {
442
        var model = {
443
          model: this.model
444
        }
445
        return model
446
      },
447

    
448
      post_init_element: function() {},
449

    
450
      init_element: function() {
451
        this.el.attr("id", "model-" + this.model.id);
452
        this.post_init_element();
453
        this.update_layout();
454
        return this.el;
455
      },
456

    
457
      update_layout: function() {}
458

    
459
    });
460
    
461
    views.ModelRenameView = views.ext.ModelView.extend({
462
      tpl: '#rename-view-tpl',
463
      title_attr: 'name',
464

    
465
      init: function() {
466
        views.ModelRenameView.__super__.init.apply(this, arguments);
467
        this.name_cont = this.$(".model-name");
468
        this.edit_cont = this.$(".edit");
469

    
470
        this.edit_btn = this.$(".edit-btn");
471
        this.value = this.$(".value");
472
        this.input = this.$("input");
473
        this.confirm = this.edit_cont.find(".confirm");
474
        this.cancel = this.edit_cont.find(".cancel");
475
        
476
        if (this.model.get('rename_disabled')) {
477
          this.edit_btn.remove();
478
        }
479

    
480
        this.value.dblclick(_.bind(function(e) {
481
          this.set_edit();
482
        }, this));
483
        this.input.bind('keyup', _.bind(function(e) {
484
          // enter keypress
485
          if (e.which == 13) { this.rename(); }
486
          // esc keypress
487
          if (e.which == 27) { this.unset_edit(); }
488
        }, this));
489
        // initial state
490
        this.unset_edit();
491
      },
492
      
493
      post_hide: function() {
494
        this.unset_edit();
495
      },
496

    
497
      set_edit: function() {
498
        if (this.model.get('rename_disabled')) { return }
499
        var self = this;
500
        this.input.val(this.model.get('name'));
501
        window.setTimeout(function() {
502
          self.input.focus();
503
        }, 20);
504
        this.name_cont.hide();
505
        this.edit_cont.show();
506
      },
507

    
508
      unset_edit: function() {
509
        this.name_cont.show();
510
        this.edit_cont.hide();
511
      },
512

    
513
      rename: function() {
514
        var value = _.trim(this.input.val());
515
        if (value) {
516
          this.model.rename(value);
517
          this.unset_edit();
518
        }
519
      }
520
    });
521

    
522
    views.ext.SelectModelView = views.ext.ModelView.extend({
523
      select: function() {
524
        if (!this.delegate_checked) {
525
          this.input.attr("checked", true);
526
          this.item.addClass("selected");
527
        }
528
        this.selected = true;
529
        this.trigger("change:select", this, this.selected);
530
      },
531

    
532
      deselect: function() {
533
        if (!this.delegate_checked) {
534
          this.input.attr("checked", false);
535
          this.item.removeClass("selected");
536
        }
537
        this.selected = false;
538
        this.trigger("change:select", this, this.selected);
539
      },
540
      
541
      toggle_select: function() {
542
        if (this.selected) { 
543
          this.deselect();
544
        } else {
545
          this.select();
546
        }
547
      },
548

    
549
      post_init_element: function() {
550
        this.input = $(this.$("input").get(0));
551
        this.item = $(this.$(".select-item").get(0));
552
        this.delegate_checked = this.model.get('noselect');
553
        this.deselect();
554

    
555
        var self = this;
556
        if (self.model.get('forced')) {
557
          this.select();
558
          this.input.attr("disabled", true);
559
          $(this.el).attr('title', this.forced_title);
560
          $(this.el).tooltip({
561
            'tipClass': 'tooltip', 
562
            'position': 'top center',
563
            'offset': [29, 0]
564
          });
565
        }
566
        
567
        $(this.item).click(function(e) {
568
          if (self.model.get('forced')) { return }
569
          e.stopPropagation();
570
          self.toggle_select();
571
        });
572
        
573
        views.ext.SelectModelView.__super__.post_init_element.apply(this,
574
                                                                    arguments);
575
      }
576
    });
577

    
578
    
579
    views.ext.ModelCreateView = views.ext.ModelView.extend({});
580
    views.ext.ModelEditView = views.ext.ModelCreateView.extend({});
581

    
582
})(this);