Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add popovers to allow elements to be edited and deleted. Implement de…

…letion.
  • Loading branch information...
commit 4cdf1a5c0c9660d70485b2ffebc581b16bc5fe71 1 parent 6893c76
Karthik Viswanathan authored July 12, 2012

Showing 37 changed files with 848 additions and 104 deletions. Show diff stats Hide diff stats

  1. 1  .jshintignore
  2. 4  lib/elements.js
  3. 4  lib/scaffold.js
  4. 24  lib/utils.js
  5. 5  public/javascripts/.jshintrc
  6. 442  public/javascripts/bootstrap.js
  7. 25  public/javascripts/bootstrap.min.js
  8. 4  public/javascripts/example.js
  9. 4  public/javascripts/main.js
  10. 210  public/javascripts/prototype.js
  11. 24  public/stylesheets/bootstrap-stripped.css
  12. 2  public/stylesheets/bootstrap-stripped.min.css
  13. 33  public/stylesheets/main.styl
  14. 34  test/test.components.js
  15. 28  test/test.elements.js
  16. 1  todo.txt
  17. 4  views/index.jade
  18. 29  views/prototype.jade
  19. 0  views/templates/{ → collections}/project.jade
  20. 0  views/templates/{ → collections}/screen.jade
  21. 6  views/templates/{heading-element.jade → elements/heading.jade}
  22. 2  views/templates/{input-checkbox-element.jade → elements/input-checkbox.jade}
  23. 2  views/templates/{input-radio-element.jade → elements/input-radio.jade}
  24. 2  views/templates/{input-text-element.jade → elements/input-text.jade}
  25. 2  views/templates/{paragraph-element.jade → elements/paragraph.jade}
  26. 2  views/templates/{textarea-element.jade → elements/textarea.jade}
  27. 0  views/templates/{ → layouts}/layout.jade
  28. 0  views/templates/{article-element-list.jade → lists/article-element.jade}
  29. 0  views/templates/{component-list.jade → lists/component.jade}
  30. 1  views/templates/{form-element-list.jade → lists/form-element.jade}
  31. 10  views/templates/popovers/footer.jade
  32. 17  views/templates/popovers/heading.jade
  33. 6  views/templates/popovers/input-checkbox.jade
  34. 6  views/templates/popovers/input-radio.jade
  35. 6  views/templates/popovers/input-text.jade
  36. 6  views/templates/popovers/paragraph.jade
  37. 6  views/templates/popovers/textarea.jade
1  .jshintignore
@@ -2,4 +2,5 @@ node_modules
2 2
 public/javascripts/backbone.min.js
3 3
 public/javascripts/underscore.min.js
4 4
 public/javascripts/bootstrap.min.js
  5
+public/javascripts/bootstrap.js
5 6
 public/javascripts/json2.js
4  lib/elements.js
@@ -5,7 +5,7 @@ const ALLOWED_FIELDS = {
5 5
   head: undefined,
6 6
 
7 7
   // > id of next element to form a linked list
8  
-  next: undefined,
  8
+  nextId: undefined,
9 9
 
10 10
   // input fields
11 11
   name: undefined,
@@ -31,7 +31,7 @@ function getDefaultElement(req) {
31 31
   return {
32 32
     type: req.body.type,
33 33
     head: req.body.head,
34  
-    next: req.body.next,
  34
+    nextId: req.body.nextId,
35 35
     name: req.body.name,
36 36
     required: req.body.required || false,
37 37
     src: req.body.src,
4  lib/scaffold.js
@@ -115,11 +115,11 @@ exports.generate = function(parents, children, name, getDefault,
115 115
     callback = callback || utils.noop;
116 116
 
117 117
     crud.update(req, key, id, allowedFields, db,
118  
-      function(err, project) {
  118
+      function(err, object) {
119 119
         if (err) {
120 120
           callback(err);
121 121
         } else {
122  
-          callback(null, project);
  122
+          callback(null, object);
123 123
         }
124 124
       });
125 125
   };
24  lib/utils.js
@@ -15,22 +15,26 @@ exports.generateUniqueId = function(name, id) {
15 15
   return name.toString().toLowerCase().replace(ALPHANUM_MATCH, '_') + id;
16 16
 };
17 17
 
18  
-/* Generically map object to req.body
  18
+/* Generically map curObject to req.body. If a property exists in curObject,
  19
+ *  req.body, and allowedFields, curObject's value is updated to equal
  20
+ *  req.body's value. If a property exists in req.body and allowedFields, but
  21
+ *  not in curObject, it is added to curObject with req.body's value.
  22
+ *
19 23
  * Requires: web request, current object, allowed fields
20 24
  * Returns: The updated object if it exists, else false
21 25
  */
22  
-exports.updateObject = function(req, currObject, allowedFields) {
23  
-  if (typeof currObject !== 'object') {
24  
-    currObject = JSON.parse(currObject);
  26
+exports.updateObject = function(req, curObject, allowedFields) {
  27
+  if (typeof curObject !== 'object') {
  28
+    curObject = JSON.parse(curObject);
25 29
   }
26 30
 
27  
-  if (currObject) {
28  
-    var setFields = function(currObject, from, allowedFields) {
  31
+  if (curObject) {
  32
+    var setFields = function(curObject, from, allowedFields) {
29 33
       var props = Object.getOwnPropertyNames(from);
30  
-      var dest = currObject;
  34
+      var dest = curObject;
31 35
 
32 36
       props.forEach(function(name) {
33  
-        if (name in dest && name in allowedFields) {
  37
+        if (name in allowedFields) {
34 38
           var destination = Object.getOwnPropertyDescriptor(from, name);
35 39
 
36 40
           if (destination) {
@@ -39,10 +43,10 @@ exports.updateObject = function(req, currObject, allowedFields) {
39 43
         }
40 44
       });
41 45
 
42  
-      return currObject;
  46
+      return curObject;
43 47
     };
44 48
 
45  
-    return setFields(currObject, req.body, allowedFields);
  49
+    return setFields(curObject, req.body, allowedFields);
46 50
   }
47 51
 
48 52
   return false;
5  public/javascripts/.jshintrc
... ...
@@ -0,0 +1,5 @@
  1
+{
  2
+    "node": true,
  3
+    "strict": false,
  4
+    "esnext": true
  5
+}
442  public/javascripts/bootstrap.js
... ...
@@ -0,0 +1,442 @@
  1
+/* ===================================================
  2
+ * bootstrap-transition.js v2.0.4
  3
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
  4
+ * ===================================================
  5
+ * Copyright 2012 Twitter, Inc.
  6
+ *
  7
+ * Licensed under the Apache License, Version 2.0 (the "License");
  8
+ * you may not use this file except in compliance with the License.
  9
+ * You may obtain a copy of the License at
  10
+ *
  11
+ * http://www.apache.org/licenses/LICENSE-2.0
  12
+ *
  13
+ * Unless required by applicable law or agreed to in writing, software
  14
+ * distributed under the License is distributed on an "AS IS" BASIS,
  15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16
+ * See the License for the specific language governing permissions and
  17
+ * limitations under the License.
  18
+ * ========================================================== */
  19
+
  20
+
  21
+!function ($) {
  22
+
  23
+  $(function () {
  24
+
  25
+    "use strict"; // jshint ;_;
  26
+
  27
+
  28
+    /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
  29
+     * ======================================================= */
  30
+
  31
+    $.support.transition = (function () {
  32
+
  33
+      var transitionEnd = (function () {
  34
+
  35
+        var el = document.createElement('bootstrap')
  36
+          , transEndEventNames = {
  37
+               'WebkitTransition' : 'webkitTransitionEnd'
  38
+            ,  'MozTransition'    : 'transitionend'
  39
+            ,  'OTransition'      : 'oTransitionEnd'
  40
+            ,  'msTransition'     : 'MSTransitionEnd'
  41
+            ,  'transition'       : 'transitionend'
  42
+            }
  43
+          , name
  44
+
  45
+        for (name in transEndEventNames){
  46
+          if (el.style[name] !== undefined) {
  47
+            return transEndEventNames[name]
  48
+          }
  49
+        }
  50
+
  51
+      }())
  52
+
  53
+      return transitionEnd && {
  54
+        end: transitionEnd
  55
+      }
  56
+
  57
+    })()
  58
+
  59
+  })
  60
+
  61
+}(window.jQuery);
  62
+/* ===========================================================
  63
+ * bootstrap-tooltip.js v2.0.4
  64
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
  65
+ * Inspired by the original jQuery.tipsy by Jason Frame
  66
+ * ===========================================================
  67
+ * Copyright 2012 Twitter, Inc.
  68
+ *
  69
+ * Licensed under the Apache License, Version 2.0 (the "License");
  70
+ * you may not use this file except in compliance with the License.
  71
+ * You may obtain a copy of the License at
  72
+ *
  73
+ * http://www.apache.org/licenses/LICENSE-2.0
  74
+ *
  75
+ * Unless required by applicable law or agreed to in writing, software
  76
+ * distributed under the License is distributed on an "AS IS" BASIS,
  77
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  78
+ * See the License for the specific language governing permissions and
  79
+ * limitations under the License.
  80
+ * ========================================================== */
  81
+
  82
+
  83
+!function ($) {
  84
+
  85
+  "use strict"; // jshint ;_;
  86
+
  87
+
  88
+ /* TOOLTIP PUBLIC CLASS DEFINITION
  89
+  * =============================== */
  90
+
  91
+  var Tooltip = function (element, options) {
  92
+    this.init('tooltip', element, options)
  93
+  }
  94
+
  95
+  Tooltip.prototype = {
  96
+
  97
+    constructor: Tooltip
  98
+
  99
+  , init: function (type, element, options) {
  100
+      var eventIn
  101
+        , eventOut
  102
+
  103
+      this.type = type
  104
+      this.$element = $(element)
  105
+      this.options = this.getOptions(options)
  106
+      this.enabled = true
  107
+
  108
+      if (this.options.trigger != 'manual') {
  109
+        eventIn  = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
  110
+        eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
  111
+        this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this))
  112
+        this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this))
  113
+      }
  114
+
  115
+      this.options.selector ?
  116
+        (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
  117
+        this.fixTitle()
  118
+    }
  119
+
  120
+  , getOptions: function (options) {
  121
+      options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
  122
+
  123
+      if (options.delay && typeof options.delay == 'number') {
  124
+        options.delay = {
  125
+          show: options.delay
  126
+        , hide: options.delay
  127
+        }
  128
+      }
  129
+
  130
+      return options
  131
+    }
  132
+
  133
+  , enter: function (e) {
  134
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
  135
+
  136
+      if (!self.options.delay || !self.options.delay.show) return self.show()
  137
+
  138
+      clearTimeout(this.timeout)
  139
+      self.hoverState = 'in'
  140
+      this.timeout = setTimeout(function() {
  141
+        if (self.hoverState == 'in') self.show()
  142
+      }, self.options.delay.show)
  143
+    }
  144
+
  145
+  , leave: function (e) {
  146
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
  147
+
  148
+      if (this.timeout) clearTimeout(this.timeout)
  149
+      if (!self.options.delay || !self.options.delay.hide) return self.hide()
  150
+
  151
+      self.hoverState = 'out'
  152
+      this.timeout = setTimeout(function() {
  153
+        if (self.hoverState == 'out') self.hide()
  154
+      }, self.options.delay.hide)
  155
+    }
  156
+
  157
+  , show: function () {
  158
+      var $tip
  159
+        , inside
  160
+        , pos
  161
+        , actualWidth
  162
+        , actualHeight
  163
+        , placement
  164
+        , tp
  165
+
  166
+      if (this.hasContent() && this.enabled) {
  167
+        $tip = this.tip()
  168
+        this.setContent()
  169
+
  170
+        if (this.options.animation) {
  171
+          $tip.addClass('fade')
  172
+        }
  173
+
  174
+        placement = typeof this.options.placement == 'function' ?
  175
+          this.options.placement.call(this, $tip[0], this.$element[0]) :
  176
+          this.options.placement
  177
+
  178
+        inside = /in/.test(placement)
  179
+
  180
+        $tip
  181
+          .remove()
  182
+          .css({ top: 0, left: 0, display: 'block' })
  183
+          .data('element', this.$element)
  184
+          .appendTo(inside ? this.$element : document.body)
  185
+
  186
+        pos = this.getPosition(inside)
  187
+
  188
+        actualWidth = $tip[0].offsetWidth
  189
+        actualHeight = $tip[0].offsetHeight
  190
+
  191
+        switch (inside ? placement.split(' ')[1] : placement) {
  192
+          case 'bottom':
  193
+            tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
  194
+            break
  195
+          case 'top':
  196
+            tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
  197
+            break
  198
+          case 'left':
  199
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
  200
+            break
  201
+          case 'right':
  202
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
  203
+            break
  204
+        }
  205
+
  206
+        $tip
  207
+          .css(tp)
  208
+          .addClass(placement)
  209
+          .addClass('in')
  210
+      }
  211
+    }
  212
+
  213
+  , isHTML: function(text) {
  214
+      // html string detection logic adapted from jQuery
  215
+      return typeof text != 'string'
  216
+        || ( text.charAt(0) === "<"
  217
+          && text.charAt( text.length - 1 ) === ">"
  218
+          && text.length >= 3
  219
+        ) || /^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(text)
  220
+    }
  221
+
  222
+  , setContent: function () {
  223
+      var $tip = this.tip()
  224
+        , title = this.getTitle()
  225
+
  226
+      $tip.find('.tooltip-inner')[this.isHTML(title) ? 'html' : 'text'](title)
  227
+      $tip.removeClass('fade in top bottom left right')
  228
+    }
  229
+
  230
+  , hide: function () {
  231
+      var that = this
  232
+        , $tip = this.tip()
  233
+
  234
+      $tip.removeClass('in')
  235
+
  236
+      function removeWithAnimation() {
  237
+        var timeout = setTimeout(function () {
  238
+          $tip.off($.support.transition.end).remove()
  239
+        }, 500)
  240
+
  241
+        $tip.one($.support.transition.end, function () {
  242
+          clearTimeout(timeout)
  243
+          $tip.remove()
  244
+        })
  245
+      }
  246
+
  247
+      $.support.transition && this.$tip.hasClass('fade') ?
  248
+        removeWithAnimation() :
  249
+        $tip.remove()
  250
+    }
  251
+
  252
+  , fixTitle: function () {
  253
+      var $e = this.$element
  254
+      if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
  255
+        $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
  256
+      }
  257
+    }
  258
+
  259
+  , hasContent: function () {
  260
+      return this.getTitle()
  261
+    }
  262
+
  263
+  , getPosition: function (inside) {
  264
+      return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
  265
+        width: this.$element[0].offsetWidth
  266
+      , height: this.$element[0].offsetHeight
  267
+      })
  268
+    }
  269
+
  270
+  , getTitle: function () {
  271
+      var title
  272
+        , $e = this.$element
  273
+        , o = this.options
  274
+
  275
+      title = $e.attr('data-original-title')
  276
+        || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
  277
+
  278
+      return title
  279
+    }
  280
+
  281
+  , tip: function () {
  282
+      return this.$tip = this.$tip || $(this.options.template)
  283
+    }
  284
+
  285
+  , validate: function () {
  286
+      if (!this.$element[0].parentNode) {
  287
+        this.hide()
  288
+        this.$element = null
  289
+        this.options = null
  290
+      }
  291
+    }
  292
+
  293
+  , enable: function () {
  294
+      this.enabled = true
  295
+    }
  296
+
  297
+  , disable: function () {
  298
+      this.enabled = false
  299
+    }
  300
+
  301
+  , toggleEnabled: function () {
  302
+      this.enabled = !this.enabled
  303
+    }
  304
+
  305
+  , toggle: function () {
  306
+      this[this.tip().hasClass('in') ? 'hide' : 'show']()
  307
+    }
  308
+
  309
+  }
  310
+
  311
+
  312
+ /* TOOLTIP PLUGIN DEFINITION
  313
+  * ========================= */
  314
+
  315
+  $.fn.tooltip = function ( option ) {
  316
+    return this.each(function () {
  317
+      var $this = $(this)
  318
+        , data = $this.data('tooltip')
  319
+        , options = typeof option == 'object' && option
  320
+      if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
  321
+      if (typeof option == 'string') data[option]()
  322
+    })
  323
+  }
  324
+
  325
+  $.fn.tooltip.Constructor = Tooltip
  326
+
  327
+  $.fn.tooltip.defaults = {
  328
+    animation: true
  329
+  , placement: 'top'
  330
+  , selector: false
  331
+  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
  332
+  , trigger: 'hover'
  333
+  , title: ''
  334
+  , delay: 0
  335
+  }
  336
+
  337
+}(window.jQuery);
  338
+
  339
+/* ===========================================================
  340
+ * bootstrap-popover.js v2.0.4
  341
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
  342
+ * ===========================================================
  343
+ * Copyright 2012 Twitter, Inc.
  344
+ *
  345
+ * Licensed under the Apache License, Version 2.0 (the "License");
  346
+ * you may not use this file except in compliance with the License.
  347
+ * You may obtain a copy of the License at
  348
+ *
  349
+ * http://www.apache.org/licenses/LICENSE-2.0
  350
+ *
  351
+ * Unless required by applicable law or agreed to in writing, software
  352
+ * distributed under the License is distributed on an "AS IS" BASIS,
  353
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  354
+ * See the License for the specific language governing permissions and
  355
+ * limitations under the License.
  356
+ * =========================================================== */
  357
+
  358
+
  359
+!function ($) {
  360
+
  361
+  "use strict"; // jshint ;_;
  362
+
  363
+
  364
+ /* POPOVER PUBLIC CLASS DEFINITION
  365
+  * =============================== */
  366
+
  367
+  var Popover = function ( element, options ) {
  368
+    this.init('popover', element, options)
  369
+  }
  370
+
  371
+
  372
+  /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
  373
+     ========================================== */
  374
+
  375
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
  376
+
  377
+    constructor: Popover
  378
+
  379
+  , setContent: function () {
  380
+      var $tip = this.tip()
  381
+        , title = this.getTitle()
  382
+        , content = this.getContent()
  383
+        , self = this
  384
+
  385
+      $tip.find('.popover-title')[this.isHTML(title) ? 'html' : 'text'](title)
  386
+
  387
+      if (this.isHTML(content)) {
  388
+        $tip.find('.popover-content')['html'](content)
  389
+      } else {
  390
+        $tip.find('.popover-content > *')['text'](content)
  391
+      }
  392
+
  393
+      $tip.removeClass('fade top bottom left right in')
  394
+    }
  395
+
  396
+  , hasContent: function () {
  397
+      return this.getTitle() || this.getContent()
  398
+    }
  399
+
  400
+  , getContent: function () {
  401
+      var content
  402
+        , $e = this.$element
  403
+        , o = this.options
  404
+
  405
+      content = $e.attr('data-content')
  406
+        || (typeof o.content == 'function' ? o.content.call($e[0]) :  o.content)
  407
+
  408
+      return content
  409
+    }
  410
+
  411
+  , tip: function () {
  412
+      if (!this.$tip) {
  413
+        this.$tip = $(this.options.template)
  414
+        this.$tip.data('popover', this);
  415
+      }
  416
+      return this.$tip
  417
+    }
  418
+  })
  419
+
  420
+
  421
+ /* POPOVER PLUGIN DEFINITION
  422
+  * ======================= */
  423
+
  424
+  $.fn.popover = function (option) {
  425
+    return this.each(function () {
  426
+      var $this = $(this)
  427
+        , data = $this.data('popover')
  428
+        , options = typeof option == 'object' && option
  429
+      if (!data) $this.data('popover', (data = new Popover(this, options)))
  430
+      if (typeof option == 'string') data[option]()
  431
+    })
  432
+  }
  433
+
  434
+  $.fn.popover.Constructor = Popover
  435
+
  436
+  $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
  437
+    placement: 'right'
  438
+  , content: ''
  439
+  , template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
  440
+  })
  441
+
  442
+}(window.jQuery);
25  public/javascripts/bootstrap.min.js
... ...
@@ -1,7 +1,18 @@
1  
-/**
2  
-* Bootstrap.js by @fat & @mdo
3  
-* plugins: bootstrap-transition.js, bootstrap-tooltip.js, bootstrap-popover.js
4  
-* Copyright 2012 Twitter, Inc.
5  
-* http://www.apache.org/licenses/LICENSE-2.0.txt
6  
-*/
7  
-!function(a){a(function(){a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,this.options.trigger!="manual"&&(e=this.options.trigger=="hover"?"mouseenter":"focus",f=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(e,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f,this.options.selector,a.proxy(this.leave,this))),this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,b,this.$element.data()),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);if(!c.options.delay||!c.options.delay.show)return c.show();clearTimeout(this.timeout),c.hoverState="in",this.timeout=setTimeout(function(){c.hoverState=="in"&&c.show()},c.options.delay.show)},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!c.options.delay||!c.options.delay.hide)return c.hide();c.hoverState="out",this.timeout=setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide)},show:function(){var a,b,c,d,e,f,g;if(this.hasContent()&&this.enabled){a=this.tip(),this.setContent(),this.options.animation&&a.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,a[0],this.$element[0]):this.options.placement,b=/in/.test(f),a.remove().css({top:0,left:0,display:"block"}).appendTo(b?this.$element:document.body),c=this.getPosition(b),d=a[0].offsetWidth,e=a[0].offsetHeight;switch(b?f.split(" ")[1]:f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}a.css(g).addClass(f).addClass("in")}},isHTML:function(a){return typeof a!="string"||a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3||/^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(a)},setContent:function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.isHTML(b)?"html":"text"](b),a.removeClass("fade in top bottom left right")},hide:function(){function d(){var b=setTimeout(function(){c.off(a.support.transition.end).remove()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.remove()})}var b=this,c=this.tip();c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d():c.remove()},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(b){return a.extend({},b?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()}},a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0}}(window.jQuery),!function(a){var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.isHTML(b)?"html":"text"](b),a.find(".popover-content > *")[this.isHTML(c)?"html":"text"](c),a.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-content")||(typeof c.content=="function"?c.content.call(b[0]):c.content),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip}}),a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'})}(window.jQuery)
  1
+/* ===================================================
  2
+ * bootstrap-transition.js v2.0.4
  3
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
  4
+ * ===================================================
  5
+ * Copyright 2012 Twitter, Inc.
  6
+ *
  7
+ * Licensed under the Apache License, Version 2.0 (the "License");
  8
+ * you may not use this file except in compliance with the License.
  9
+ * You may obtain a copy of the License at
  10
+ *
  11
+ * http://www.apache.org/licenses/LICENSE-2.0
  12
+ *
  13
+ * Unless required by applicable law or agreed to in writing, software
  14
+ * distributed under the License is distributed on an "AS IS" BASIS,
  15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16
+ * See the License for the specific language governing permissions and
  17
+ * limitations under the License.
  18
+ * ========================================================== */!function(e){e(function(){"use strict";e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,this.options.trigger!="manual"&&(i=this.options.trigger=="hover"?"mouseenter":"focus",s=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(i,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s,this.options.selector,e.proxy(this.leave,this))),this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,t,this.$element.data()),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var e,t,n,r,i,s,o;if(this.hasContent()&&this.enabled){e=this.tip(),this.setContent(),this.options.animation&&e.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,t=/in/.test(s),e.remove().css({top:0,left:0,display:"block"}).data("element",this.$element).appendTo(t?this.$element:document.body),n=this.getPosition(t),r=e[0].offsetWidth,i=e[0].offsetHeight;switch(t?s.split(" ")[1]:s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}e.css(o).addClass(s).addClass("in")}},isHTML:function(e){return typeof e!="string"||e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3||/^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(e)},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.isHTML(t)?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function r(){var t=setTimeout(function(){n.off(e.support.transition.end).remove()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.remove()})}var t=this,n=this.tip();n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?r():n.remove()},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(t){return e.extend({},t?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()}},e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent(),r=this;e.find(".popover-title")[this.isHTML(t)?"html":"text"](t),this.isHTML(n)?e.find(".popover-content").html(n):e.find(".popover-content > *").text(n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-content")||(typeof n.content=="function"?n.content.call(t[0]):n.content),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template),this.$tip.data("popover",this)),this.$tip}}),e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'})}(window.jQuery);
4  public/javascripts/example.js
@@ -23,7 +23,7 @@
23 23
       // outerWidth/outerHeight = width/height with padding/border
24 24
       width: $sidebar.outerWidth(),
25 25
       height: $sidebar.outerHeight() - 70,
26  
-      backgroundColor: 'rgba(0, 0, 0, 0.4)',
  26
+      backgroundColor: 'rgba(0, 0, 0, 0.4)'
27 27
     }).appendTo('body');
28 28
 
29 29
     $('<div/>').css({
@@ -33,7 +33,7 @@
33 33
       // outerWidth/outerHeight = width/height with padding/border
34 34
       width: $body.outerWidth() - $sidebar.outerWidth(),
35 35
       height: $sidebar.outerHeight(),
36  
-      backgroundColor: 'rgba(0, 0, 0, 0.4)',
  36
+      backgroundColor: 'rgba(0, 0, 0, 0.4)'
37 37
     }).appendTo('body');
38 38
 
39 39
     $loginForm.children('a').popover({
4  public/javascripts/main.js
@@ -288,7 +288,7 @@ function NapkinClient(window, document, $, data, undefined) {
288 288
       var that = this;
289 289
 
290 290
       projects.create({
291  
-        title: this.$projectInput.val().trim(),
  291
+        title: this.$projectInput.val().trim()
292 292
       }, {
293 293
         error: tooltipErrorHandler(this.$projectInput),
294 294
         success: function(model) {
@@ -303,7 +303,7 @@ function NapkinClient(window, document, $, data, undefined) {
303 303
       var that = this;
304 304
 
305 305
       screens.create({
306  
-        title: this.$screenInput.val().trim(),
  306
+        title: this.$screenInput.val().trim()
307 307
       }, {
308 308
         error: tooltipErrorHandler(this.$screenInput),
309 309
         success: function(model) {
210  public/javascripts/prototype.js
@@ -83,7 +83,7 @@ function NapkinClient(window, document, $, data, undefined) {
83 83
       var $element = $target.siblings('.element').clone();
84 84
 
85 85
       $element.removeClass('element')
86  
-        .addClass('active-element');
  86
+        .addClass('live-element');
87 87
       this.trigger('createElement', $element);
88 88
     }
89 89
   });
@@ -97,24 +97,18 @@ function NapkinClient(window, document, $, data, undefined) {
97 97
     $componentClicked: null,
98 98
 
99 99
     events: {
100  
-      'click [class^="span"]': 'recordActiveComponentClick',
101  
-      'click': 'blurComponent'
  100
+      'click [class^="span"]': 'recordComponentClick',
  101
+      'click .live-element': 'recordElementClick',
  102
+      'click': 'switchFocus'
102 103
     },
103 104
 
104  
-    selectComponent: function($component, model) {
105  
-      $component.data('model', model);
106  
-      $component.addClass(model.get('type') + '-container');
107  
-      $component.addClass('active');
  105
+    initialize: function() {
  106
+      componentGroup.bind('add', this.addComponent, this);
  107
+      componentGroup.bind('reset', this.addAllComponents, this);
108 108
 
109  
-      // trigger a selectComponent event which the AppView will handle
110  
-      this.$activeComponent = $component;
111  
-      this.trigger('selectComponent', $component);
112  
-    },
113  
-
114  
-    resetComponent: function($component, type) {
115  
-      $component.removeClass(type + '-container');
116  
-      $component.removeClass('active');
117  
-      $component.empty();
  109
+      this.bind('closePopover', this.resetActiveElement, this);
  110
+      this.bind('applyEdits', this.applyEdits, this);
  111
+      this.bind('removeElement', this.removeElement, this);
118 112
     },
119 113
 
120 114
     getDropOptions: function(layoutView) {
@@ -167,11 +161,6 @@ function NapkinClient(window, document, $, data, undefined) {
167 161
       };
168 162
     },
169 163
 
170  
-    initialize: function() {
171  
-      componentGroup.bind('add', this.addComponent, this);
172  
-      componentGroup.bind('reset', this.addAllComponents, this);
173  
-    },
174  
-
175 164
     render: function() {
176 165
       var that = this;
177 166
       this.$el.html(this.template({}));
@@ -198,6 +187,22 @@ function NapkinClient(window, document, $, data, undefined) {
198 187
       return this;
199 188
     },
200 189
 
  190
+    selectComponent: function($component, model) {
  191
+      $component.data('model', model);
  192
+      $component.addClass(model.get('type') + '-container');
  193
+      $component.addClass('active');
  194
+
  195
+      // trigger a selectComponent event which the AppView will handle
  196
+      this.$activeComponent = $component;
  197
+      this.trigger('selectComponent', $component);
  198
+    },
  199
+
  200
+    resetComponent: function($component, type) {
  201
+      $component.removeClass(type + '-container');
  202
+      $component.removeClass('active');
  203
+      $component.empty();
  204
+    },
  205
+
201 206
     addComponent: function(componentModel) {
202 207
       var layout = componentModel.get('layout');
203 208
       var selector = '[data-position="' + layout.row + ':' + layout.col +'"]';
@@ -231,16 +236,25 @@ function NapkinClient(window, document, $, data, undefined) {
231 236
       });
232 237
     },
233 238
 
234  
-    recordActiveComponentClick: function(event) {
235  
-      var $component = $(event.currentTarget);
236  
-      if ($component.is(this.$activeComponent)) {
  239
+    recordComponentClick: function(event) {
  240
+      var $target = $(event.currentTarget);
  241
+      this.$componentClicked = $target;
  242
+
  243
+      if ($target.is(this.$activeComponent)) {
237 244
         this.clickedActiveComponent = true;
238 245
       }
  246
+    },
239 247
 
240  
-      this.$componentClicked = $component;
  248
+    recordElementClick: function(event) {
  249
+      var $target = $(event.currentTarget);
  250
+      this.$elementClicked = $target;
  251
+
  252
+      if ($target.is(this.$activeElement)) {
  253
+        this.clickedActiveElement = true;
  254
+      }
241 255
     },
242 256
 
243  
-    blurComponent: function(event) {
  257
+    switchFocus: function(event) {
244 258
       // note that clickedActiveComponent will be set by the
245 259
       // recordComponentClick function due to event bubbling
246 260
       if (!this.clickedActiveComponent) {
@@ -250,6 +264,7 @@ function NapkinClient(window, document, $, data, undefined) {
250 264
         if (this.$activeComponent) {
251 265
           this.$activeComponent.removeClass('active');
252 266
           this.trigger('blurComponent');
  267
+          this.$activeComponent = null;
253 268
         }
254 269
 
255 270
         // if a component was clicked, put it in focus
@@ -259,14 +274,45 @@ function NapkinClient(window, document, $, data, undefined) {
259 274
             this.trigger('selectComponent', $component);
260 275
             $component.addClass('active');
261 276
             this.$activeComponent = $component;
262  
-          } else {
263  
-            this.$activeComponent = null;
264 277
           }
265 278
         }
266 279
       }
267 280
 
  281
+      if (!this.clickedActiveElement) {
  282
+        var $element = this.$elementClicked;
  283
+
  284
+        // active element may not be defined if nothing is in focus
  285
+        this.resetActiveElement();
  286
+
  287
+        // if an element was clicked, put it in focus
  288
+        if ($element) {
  289
+          this.setActiveElement($element);
  290
+        }
  291
+      }
  292
+
  293
+      // reset click data
268 294
       this.clickedActiveComponent = false;
  295
+      this.clickedActiveElement = false;
269 296
       this.$componentClicked = null;
  297
+      this.$elementClicked = null;
  298
+    },
  299
+
  300
+    setActiveElement: function($element) {
  301
+      $element.popover('show');
  302
+      $element.addClass('active');
  303
+      this.$activeElement = $element;
  304
+    },
  305
+
  306
+    resetActiveElement: function() {
  307
+      var $element = this.$activeElement;
  308
+      if ($element) {
  309
+        $element.popover('hide');
  310
+        $element.removeClass('active');
  311
+      }
  312
+
  313
+      this.$activeElement = null;
  314
+      this.clickedActiveElement = false;
  315
+      this.$elementClicked = null;
270 316
     },
271 317
 
272 318
     createElement: function($element) {
@@ -281,7 +327,7 @@ function NapkinClient(window, document, $, data, undefined) {
281 327
 
282 328
       var elementAttrs = {
283 329
         type: $element.data('type'),
284  
-        next: null,
  330
+        nextId: null,
285 331
         name: $element.data('name'),
286 332
         required: false,
287 333
         src: $element.data('src'),
@@ -290,7 +336,7 @@ function NapkinClient(window, document, $, data, undefined) {
290 336
       };
291 337
 
292 338
       // if there's no last element, this must be the head
293  
-      var last = elementGroup.where({ next: null })[0];
  339
+      var last = elementGroup.where({ nextId: null })[0];
294 340
       if (!last) {
295 341
         elementAttrs.head = true;
296 342
       }
@@ -299,7 +345,7 @@ function NapkinClient(window, document, $, data, undefined) {
299 345
         // TODO: handle error
300 346
         success: function(model) {
301 347
           if (last) {
302  
-            last.set('next', model.id);
  348
+            last.set('nextId', model.id);
303 349
             last.save();
304 350
           }
305 351
         }
@@ -308,25 +354,93 @@ function NapkinClient(window, document, $, data, undefined) {
308 354
 
309 355
     addElement: function(element, $component) {
310 356
       var templateId = element.get('type') + '-element-template';
311  
-      var template = _.template($('#' + templateId).html());
312  
-      $(template(element.toJSON())).appendTo($component);
  357
+      var elementTemplate = _.template($('#' + templateId).html());
  358
+
  359
+      templateId = element.get('type') + '-popover-template';
  360
+      var popoverTemplate = _.template($('#' + templateId).html());
  361
+
  362
+      var $element = $(elementTemplate(element.toJSON()));
  363
+      var row = $component.data('position').row;
  364
+
  365
+      var placement = 'right';
  366
+      if (row === 0) {
  367
+        placement = 'bottom';
  368
+      } else if (row === 2) {
  369
+        placement = 'top';
  370
+      }
  371
+
  372
+      $element.appendTo($component);
  373
+      $element.data('model', element);
  374
+
  375
+      $element.popover({
  376
+          title: 'Edit Element',
  377
+          trigger: 'manual',
  378
+          placement: placement,
  379
+          content: popoverTemplate(element.toJSON())
  380
+        });
313 381
     },
314 382
 
315 383
     addAllElements: function($component) {
  384
+      // clear out any leftover elements
  385
+      $component.empty();
  386
+
316 387
       var elementGroup = $component.data('elementGroup');
317 388
       var element = elementGroup.where({ head: true })[0];
318 389
 
319 390
       // go through each element in the linked list
320 391
       while (element) {
321 392
         this.addElement(element, $component);
322  
-        element = elementGroup.get(element.get('next'));
  393
+        element = elementGroup.get(element.get('nextId'));
323 394
       }
  395
+    },
  396
+
  397
+    applyEdits: function() {
  398
+      var $element = this.$activeElement;
  399
+
  400
+    },
  401
+
  402
+    removeElement: function() {
  403
+      var $element = this.$activeElement;
  404
+      this.resetActiveElement();
  405
+      var model = $element.data('model');
  406
+
  407
+      // $element.parent() is the component
  408
+      var elementGroup = $element.parent().data('elementGroup');
  409
+
  410
+      var previous = elementGroup.where({ nextId: model.id })[0];
  411
+      var nextId = model.get('nextId');
  412
+
  413
+      model.destroy({
  414
+        success: function() {
  415
+          if (previous) {
  416
+            // rearrange links in the linked list
  417
+            previous.set('nextId', nextId);
  418
+            previous.save();
  419
+          } else {
  420
+            // removing the first element, so reset the head
  421
+            var next = elementGroup.get(nextId);
  422
+            if (next) { // may also be the last element
  423
+              next.set('head', true);
  424
+              next.save();
  425
+            }
  426
+          }
  427
+
  428
+          $element.remove();
  429
+        }
  430
+      });
324 431
     }
325 432
   });
326 433
 
327 434
   var AppView = Backbone.View.extend({
328 435
     el: $('body'),
329 436
 
  437
+    events: {
  438
+      'click .close-popover': 'closePopover',
  439
+      'click .popover-content .btn-primary': 'applyEdits',
  440
+      'click .popover-content .btn-danger': 'removeElement',
  441
+      'keydown': 'processKeyShortcuts'
  442
+    },
  443
+
330 444
     initialize: function() {
331 445
       this.componentListView = new ComponentListView();
332 446
       $('#sidebar').append(this.componentListView.render().$el);
@@ -359,6 +473,34 @@ function NapkinClient(window, document, $, data, undefined) {
359 473
         this.elementListView.unbind('addElement');
360 474
         this.elementListView.remove();
361 475
       }
  476
+    },
  477
+
  478
+    // close popover when close popover link is clicked
  479
+    closePopover: function(event) {
  480
+      event.preventDefault();
  481
+      this.layoutView.trigger('closePopover');
  482
+    },
  483
+
  484
+    // apply edits to element when apply button is clicked
  485
+    applyEdits: function(event) {
  486
+      this.layoutView.trigger('applyEdits');
  487
+    },
  488
+
  489
+    // remove element when delete button is clicked
  490
+    removeElement: function(event) {
  491
+      this.layoutView.trigger('removeElement');
  492
+    },
  493
+
  494
+    // process key presses for shortcuts; e.g. backspace deletes an element
  495
+    processKeyShortcuts: function(event) {
  496
+      // ignore key presses in input fields
  497
+      if ($('input[type="text"]:focus, textarea:focus').length === 0) {
  498
+        // if the user hit backspace and has an element focused, remove it
  499
+        if (event.which === 8 && this.layoutView.$activeElement) {
  500
+          this.layoutView.trigger('removeElement');
  501
+          event.preventDefault();
  502
+        }
  503
+      }
362 504
     }
363 505
   });
364 506
 
24  public/stylesheets/bootstrap-stripped.css
@@ -219,6 +219,24 @@ textarea {
219 219
 .offset1 {
220 220
   margin-left: 100px;
221 221
 }
  222
+.form-actions {
  223
+  padding: 17px 20px 18px;
  224
+  margin-top: 18px;
  225
+  margin-bottom: 18px;
  226
+  background-color: #f5f5f5;
  227
+  border-top: 1px solid #e5e5e5;
  228
+  *zoom: 1;
  229
+}
  230
+
  231
+.form-actions:before,
  232
+.form-actions:after {
  233
+  display: table;
  234
+  content: "";
  235
+}
  236
+
  237
+.form-actions:after {
  238
+  clear: both;
  239
+}
222 240
 select,
223 241
 input[type="text"],
224 242
 textarea {
@@ -662,6 +680,12 @@ input[type="submit"].btn::-moz-focus-inner {
662 680
 .icon-plus-sign {
663 681
   background-position: 0 -96px;
664 682
 }
  683
+.icon-remove {
  684
+  background-position: -312px 0;
  685
+}
  686
+.icon-refresh {
  687
+  background-position: -240px -24px;
  688
+}
665 689
 .tooltip {
666 690
   position: absolute;
667 691
   z-index: 1020;
2  public/stylesheets/bootstrap-stripped.min.css
... ...
@@ -1 +1 @@
1  
-/*!* Bootstrap v2.0.4 * * Copyright 2012 Twitter,Inc * Licensed under the Apache License v2.0 * http://www.apache.org/licenses/LICENSE-2.0 * * Designed and built with all the love in the world @twitter by @mdo and @fat. */ .clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";}.clearfix:after{clear:both;}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;}.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}audio:not([controls]){display:none;}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}a:hover,a:active{outline:0;}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;}#map_canvas img{max-width:none;}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}button,input{*overflow:visible;line-height:normal;}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";}.row:after{clear:both;}[class*="span"]{float:left;margin-left:20px;}.span12{width:940px;}.span11{width:860px;}.span10{width:780px;}.span9{width:700px;}.span8{width:620px;}.span7{width:540px;}.span6{width:460px;}.span5{width:380px;}.span4{width:300px;}.span3{width:220px;}.span2{width:140px;}.span1{width:60px;}.offset12{margin-left:980px;}.offset11{margin-left:900px;}.offset10{margin-left:820px;}.offset9{margin-left:740px;}.offset8{margin-left:660px;}.offset7{margin-left:580px;}.offset6{margin-left:500px;}.offset5{margin-left:420px;}.offset4{margin-left:340px;}.offset3{margin-left:260px;}.offset2{margin-left:180px;}.offset1{margin-left:100px;}select,input[type="text"],textarea{display:inline-block;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555;}input,textarea{width:210px;}input[type="text"],textarea{background-color:#fff;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-ms-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;}input[type="text"]:focus,textarea:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);}input[type="radio"],input[type="checkbox"]{margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer;}input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;}.uneditable-textarea{width:auto;height:auto;}select{height:28px;*margin-top:4px;line-height:28px;}select{width:220px;border:1px solid #bbb;}select[multiple],select[size]{height:auto;}select:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}.radio,.checkbox{min-height:18px;padding-left:18px;}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;}.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 10px 4px;margin-bottom:0;font-size:13px;line-height:18px;*line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(top,#fff,#e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff',endColorstr='#e6e6e6',GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #ccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9;}.btn:active,.btn.active{background-color:#ccc \9;}.btn:first-child{*margin-left:0;}.btn:hover{color:#333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-ms-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear;}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);}.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75);}.btn{border-color:#ccc;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);}.btn-primary{background-color:#0074cc;background-image:-moz-linear-gradient(top,#08c,#05c);background-image:-ms-linear-gradient(top,#08c,#05c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#05c));background-image:-webkit-linear-gradient(top,#08c,#05c);background-image:-o-linear-gradient(top,#08c,#05c);background-image:linear-gradient(top,#08c,#05c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc',endColorstr='#0055cc',GradientType=0);border-color:#05c #0055cc #003580;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#05c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#05c;*background-color:#004ab3;}.btn-primary:active,.btn-primary.active{background-color:#004099 \9;}.btn-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-ms-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(top,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450',endColorstr='#f89406',GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;*background-color:#df8505;}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;}.btn-danger{background-color:#da4f49;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-ms-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(top,#ee5f5b,#bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#bd362f',GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a;}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;}.btn-success{background-color:#5bb75b;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-ms-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(top,#62c462,#51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462',endColorstr='#51a351',GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249;}.btn-success:active,.btn-success.active{background-color:#408140 \9;}.btn-info{background-color:#49afcd;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-ms-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(top,#5bc0de,#2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de',endColorstr='#2f96b4',GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0;}.btn-info:active,.btn-info.active{background-color:#24748c \9;}.btn-inverse{background-color:#414141;background-image:-moz-linear-gradient(top,#555,#222);background-image:-ms-linear-gradient(top,#555,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#555),to(#222));background-image:-webkit-linear-gradient(top,#555,#222);background-image:-o-linear-gradient(top,#555,#222);background-image:linear-gradient(top,#555,#222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555',endColorstr='#222222',GradientType=0);border-color:#222 #222222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222;*background-color:#151515;}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;}button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../images/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;}.icon-white{background-image:url("../images/glyphicons-halflings-white.png");}.icon-chevron-right{background-position:-456px -72px;}.icon-pencil{background-position:0 -72px;}.icon-trash{background-position:-456px 0;}.icon-plus-sign{background-position:0 -96px;}.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:.8;filter:alpha(opacity=80);}.tooltip.top{margin-top:-2px;}.tooltip.right{margin-left:2px;}.tooltip.bottom{margin-top:2px;}.tooltip.left{margin-left:-2px;}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000;}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000;}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000;}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000;}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.tooltip-arrow{position:absolute;width:0;height:0;}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-ms-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear;}.fade.in{opacity:1;}.modal-open .popover{z-index:2060;}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;}.popover.top{margin-top:-5px;}.popover.right{margin-left:5px;}.popover.bottom{margin-top:5px;}.popover.left{margin-left:-5px;}.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000;border-right:5px solid transparent;border-left:5px solid transparent;}.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000;border-bottom:5px solid transparent;}.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000;border-left:5px solid transparent;}.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000;}.popover .arrow{position:absolute;width:0;height:0;}.popover-inner{width:280px;padding:3px;overflow:hidden;background:#000;background:rgba(0,0,0,0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);}.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;}.popover-content{padding:14px;background-color:#fff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;}
  1
+/*!* Bootstrap v2.0.4 * * Copyright 2012 Twitter,Inc * Licensed under the Apache License v2.0 * http://www.apache.org/licenses/LICENSE-2.0 * * Designed and built with all the love in the world @twitter by @mdo and @fat. */ .clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";}.clearfix:after{clear:both;}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;}.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}audio:not([controls]){display:none;}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}a:hover,a:active{outline:0;}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;}#map_canvas img{max-width:none;}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}button,input{*overflow:visible;line-height:normal;}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";}.row:after{clear:both;}[class*="span"]{float:left;margin-left:20px;}.span12{width:940px;}.span11{width:860px;}.span10{width:780px;}.span9{width:700px;}.span8{width:620px;}.span7{width:540px;}.span6{width:460px;}.span5{width:380px;}.span4{width:300px;}.span3{width:220px;}.span2{width:140px;}.span1{width:60px;}.offset12{margin-left:980px;}.offset11{margin-left:900px;}.offset10{margin-left:820px;}.offset9{margin-left:740px;}.offset8{margin-left:660px;}.offset7{margin-left:580px;}.offset6{margin-left:500px;}.offset5{margin-left:420px;}.offset4{margin-left:340px;}.offset3{margin-left:260px;}.offset2{margin-left:180px;}.offset1{margin-left:100px;}.form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";}.form-actions:after{clear:both;}select,input[type="text"],textarea{display:inline-block;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555;}input,textarea{width:210px;}input[type="text"],textarea{background-color:#fff;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-ms-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;}input[type="text"]:focus,textarea:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);}input[type="radio"],input[type="checkbox"]{margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer;}input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;}.uneditable-textarea{width:auto;height:auto;}select{height:28px;*margin-top:4px;line-height:28px;}select{width:220px;border:1px solid #bbb;}select[multiple],select[size]{height:auto;}select:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}.radio,.checkbox{min-height:18px;padding-left:18px;}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;}.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 10px 4px;margin-bottom:0;font-size:13px;line-height:18px;*line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(top,#fff,#e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff',endColorstr='#e6e6e6',GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #ccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9;}.btn:active,.btn.active{background-color:#ccc \9;}.btn:first-child{*margin-left:0;}.btn:hover{color:#333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-ms-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear;}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);}.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75);}.btn{border-color:#ccc;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);}.btn-primary{background-color:#0074cc;background-image:-moz-linear-gradient(top,#08c,#05c);background-image:-ms-linear-gradient(top,#08c,#05c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#05c));background-image:-webkit-linear-gradient(top,#08c,#05c);background-image:-o-linear-gradient(top,#08c,#05c);background-image:linear-gradient(top,#08c,#05c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc',endColorstr='#0055cc',GradientType=0);border-color:#05c #0055cc #003580;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#05c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#05c;*background-color:#004ab3;}.btn-primary:active,.btn-primary.active{background-color:#004099 \9;}.btn-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-ms-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(top,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450',endColorstr='#f89406',GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;*background-color:#df8505;}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;}.btn-danger{background-color:#da4f49;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-ms-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(top,#ee5f5b,#bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#bd362f',GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a;}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;}.btn-success{background-color:#5bb75b;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-ms-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(top,#62c462,#51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462',endColorstr='#51a351',GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249;}.btn-success:active,.btn-success.active{background-color:#408140 \9;}.btn-info{background-color:#49afcd;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-ms-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(top,#5bc0de,#2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de',endColorstr='#2f96b4',GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0;}.btn-info:active,.btn-info.active{background-color:#24748c \9;}.btn-inverse{background-color:#414141;background-image:-moz-linear-gradient(top,#555,#222);background-image:-ms-linear-gradient(top,#555,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#555),to(#222));background-image:-webkit-linear-gradient(top,#555,#222);background-image:-o-linear-gradient(top,#555,#222);background-image:linear-gradient(top,#555,#222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555',endColorstr='#222222',GradientType=0);border-color:#222 #222222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222;*background-color:#151515;}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;}button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../images/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;}.icon-white{background-image:url("../images/glyphicons-halflings-white.png");}.icon-chevron-right{background-position:-456px -72px;}.icon-pencil{background-position:0 -72px;}.icon-trash{background-position:-456px 0;}.icon-plus-sign{background-position:0 -96px;}.icon-remove{background-position:-312px 0;}.icon-refresh{background-position:-240px -24px;}.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:.8;filter:alpha(opacity=80);}.tooltip.top{margin-top:-2px;}.tooltip.right{margin-left:2px;}.tooltip.bottom{margin-top:2px;}.tooltip.left{margin-left:-2px;}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000;}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000;}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000;}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000;}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.tooltip-arrow{position:absolute;width:0;height:0;}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-ms-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear;}.fade.in{opacity:1;}.modal-open .popover{z-index:2060;}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;}.popover.top{margin-top:-5px;}.popover.right{margin-left:5px;}.popover.bottom{margin-top:5px;}.popover.left{margin-left:-5px;}.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000;border-right:5px solid transparent;border-left:5px solid transparent;}.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000;border-bottom:5px solid transparent;}.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000;border-left:5px solid transparent;}.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000;}.popover .arrow{position:absolute;width:0;height:0;}.popover-inner{width:280px;padding:3px;overflow:hidden;background:#000;background:rgba(0,0,0,0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);}.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;}.popover-content{padding:14px;background-color:#fff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;}
33  public/stylesheets/main.styl
@@ -295,10 +295,15 @@ header a
295 295
   float left
296 296
   width 190px
297 297
 
298  
-.active-element:hover
  298
+.live-element:hover, .live-element.active
299 299
   background rgba(78, 150, 0, 0.3)
300 300
   background #F7F3C5
301 301
 
  302
+.live-element.active
  303
+  padding 5px 10px
  304
+  margin-left -10px
  305
+  margin-right -10px
  306
+
302 307
 .elements .btn
303 308
   float right
304 309
   font-size 110%
@@ -338,3 +343,29 @@ header a
338 343
 
339 344
 #prototype #content h2
340 345
   margin-bottom 20px
  346
+
  347
+.popover-inner
  348
+  width 260px
  349
+
  350
+.popover-content .form-actions
  351
+  margin 10px -14px -14px
  352
+
  353
+.popover-content .form-actions .btn-primary
  354
+  margin-right 10px
  355
+
  356
+.popover-content .form-actions i
  357
+  margin-top 1px
  358
+  margin-right 3px
  359
+
  360
+.close-popover
  361
+  position absolute
  362
+  right -5px
  363
+  top -5px
  364
+  padding 5px 5px 2px
  365
+  background-color rgba(0, 0, 0, 0.9)
  366
+  border-radius(15px)
  367
+
  368
+#level
  369
+  width 45px
  370
+  margin-left 10px
  371
+  margin-top 7px
34  test/test.components.js
@@ -45,7 +45,10 @@ var componentReq = {
45 45
   },
46 46
   body: {
47 47
     type: 'form',
48  
-    layout: 'row1',
  48
+    layout: {
  49
+      row: 1,
  50
+      col: 0
  51
+    },
49 52
     action: '/'
50 53
   },
51 54
   params: {
@@ -60,7 +63,10 @@ var otherComponentReq = {
60 63
   },
61 64
   body: {
62 65
     type: 'authentication',
63  
-    layout: 'row1',
  66
+    layout: {
  67
+      row: 1,
  68
+      col: 1
  69
+    },
64 70
     action: '/auth'
65 71
   },
66 72
   params: {
@@ -107,7 +113,7 @@ describe('component', function() {
107 113
 
108 114
         components.add(req, db, function(err, component) {
109 115
           component.type.should.equal(req.body.type);
110  
-          component.layout.should.equal(req.body.layout);
  116
+          component.layout.should.eql(req.body.layout);
111 117
           component.action.should.equal(req.body.action);
112 118
           done();
113 119
         });
@@ -121,7 +127,7 @@ describe('component', function() {
121 127
         setTimeout(function() {
122 128
           components.get(req, db, 2, function(err, component) {
123 129
             component.type.should.equal(req.body.type);
124  
-            component.layout.should.equal(req.body.layout);
  130
+            component.layout.should.eql(req.body.layout);
125 131
             component.action.should.equal(req.body.action);
126 132
             done();
127 133
           });
@@ -136,12 +142,12 @@ describe('component', function() {
136 142
 
137 143
         components.list(req, db, function(errList, componentList) {
138 144
           componentList[0].type.should.equal(req.body.type);
139  
-          componentList[0].layout.should.equal(req.body.layout);
  145
+          componentList[0].layout.should.eql(req.body.layout);
140 146
           componentList[0].action.should.equal(req.body.action);
141 147
 
142 148
           req = otherComponentReq;
143 149
           componentList[1].type.should.equal(req.body.type);