Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (14.8 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
        this.container = options && options.container;
39
        this._subviews = [];
40
        if (this.tpl) {
41
          this.el = $(this.tpl).clone().removeClass("hidden").removeAttr('id');
42
        }
43
        this.init.apply(this, arguments);
44
        this.post_init.apply(this, arguments);
45
        this.append_to_container();
46
        $(this.el).addClass(this.classes);
47
        _.bindAll(this);
48
      },
49
      
50
      append_to_container: function() {
51
        if (!this.container) { return }
52
        var cont = $(this.container);
53
        cont.append(this.el);
54
      },
55

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

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

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

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

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

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

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

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

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

    
167
      set_handlers: function() {
168
        this.each_storage_handler(this.set_handler, this);
169
      },
170

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

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

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

    
195
    views.ext.CollectionView = views.ext.View.extend({
196
      collection: undefined,
197
      model_view_cls: undefined,
198
      animation_speed: 200,
199

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

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

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

    
250
      handle_collection_change: function() {
251
        this.update_models();
252
      },
253

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

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

    
266
      hide_empty: function() {
267
        this.empty_el.hide();
268
      },
269

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

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

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

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

    
335
      add_model_view: function(view, model, index) {
336
        // append html element to the parent
337
        var el = view.init_element();
338
        // append to registry object
339
        this._model_views[model.id] = view;
340
        el.addClass("model-item");
341
        // where to place ?
342
        var parent = this.parent_for_model(model);
343
        // append
344
        this.place_in_parent(parent, el, model, index);
345
        // make it visible by default
346
        this.add_subview(view);
347
        view.show(true);
348
      },
349
      
350
      each_model_view: function(cb, context) {
351
        if (!context) { context = this };
352
        _.each(this._model_views, function(view, model_id){
353
          var model = this.collection.get(model_id);
354
          cb.call(this, model, view, model_id);
355
        }, this);
356
      },
357

    
358
      remove_model: function(m) {
359
        console.log("REMOVING MODEL", m);
360
        var model_view = this._model_views[m.id];
361
        if (!model_view) {
362
          console.error("no view found");
363
          return;
364
        }
365
        model_view.hide();
366
        model_view.el.remove();
367
        this.remove_view(model_view);
368
        delete this._model_views[m.id];
369
        this.check_empty();
370
      },
371

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

    
396
    views.ext.ModelView = views.ext.View.extend({
397
      rivets_view: true,
398
      
399
      initialize: function() {
400
        views.ext.ModelView.__super__.initialize.apply(this, arguments);
401
        var actions = this.model.get('actions');
402
        if (actions) {
403
          this.init_action_methods(this.model.get('actions'));
404
          this.bind("hide", function() {
405
            actions.reset_pending();
406
          });
407
        }
408
      },
409
      
410
      set_confirm: function() {},
411
      unset_confirm: function() {},
412

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

    
435
      get_rivet_object: function() {
436
        var model = {
437
          model: this.model
438
        }
439
        return model
440
      },
441

    
442
      post_init_element: function() {},
443

    
444
      init_element: function() {
445
        this.el.attr("id", "model-" + this.model.id);
446
        this.post_init_element();
447
        this.update_layout();
448
        return this.el;
449
      },
450

    
451
      update_layout: function() {}
452

    
453
    });
454
    
455
    views.ModelRenameView = views.ext.ModelView.extend({
456
      tpl: '#rename-view-tpl',
457
      title_attr: 'name',
458

    
459
      init: function() {
460
        views.ModelRenameView.__super__.init.apply(this, arguments);
461
        this.name_cont = this.$(".model-name");
462
        this.edit_cont = this.$(".edit");
463

    
464
        this.edit_btn = this.$(".edit-btn");
465
        this.value = this.$(".value");
466
        this.input = this.$("input");
467
        this.confirm = this.edit_cont.find(".confirm");
468
        this.cancel = this.edit_cont.find(".cancel");
469
        
470
        if (this.model.get('rename_disabled')) {
471
          this.edit_btn.remove();
472
        }
473

    
474
        this.value.dblclick(_.bind(function(e) {
475
          this.set_edit();
476
        }, this));
477
        this.input.bind('keyup', _.bind(function(e) {
478
          // enter keypress
479
          if (e.which == 13) { this.rename(); }
480
          // esc keypress
481
          if (e.which == 27) { this.unset_edit(); }
482
        }, this));
483
        // initial state
484
        this.unset_edit();
485
      },
486
      
487
      post_hide: function() {
488
        this.unset_edit();
489
      },
490

    
491
      set_edit: function() {
492
        if (this.model.get('rename_disabled')) { return }
493
        var self = this;
494
        this.input.val(this.model.get('name'));
495
        window.setTimeout(function() {
496
          self.input.focus();
497
        }, 20);
498
        this.name_cont.hide();
499
        this.edit_cont.show();
500
      },
501

    
502
      unset_edit: function() {
503
        this.name_cont.show();
504
        this.edit_cont.hide();
505
      },
506

    
507
      rename: function() {
508
        var value = _.trim(this.input.val());
509
        if (value) {
510
          this.model.rename(value);
511
          this.unset_edit();
512
        }
513
      }
514
    });
515
    
516
    views.ext.ModelCreateView = views.ext.ModelView.extend({});
517
    views.ext.ModelEditView = views.ext.ModelCreateView.extend({});
518

    
519
})(this);