From 4cdf1a5c0c9660d70485b2ffebc581b16bc5fe71 Mon Sep 17 00:00:00 2001 From: Karthik Viswanathan Date: Thu, 12 Jul 2012 10:16:56 -0700 Subject: [PATCH] Add popovers to allow elements to be edited and deleted. Implement deletion. --- .jshintignore | 1 + lib/elements.js | 4 +- lib/scaffold.js | 4 +- lib/utils.js | 24 +- public/javascripts/.jshintrc | 5 + public/javascripts/bootstrap.js | 442 ++++++++++++++++++ public/javascripts/bootstrap.min.js | 25 +- public/javascripts/example.js | 4 +- public/javascripts/main.js | 4 +- public/javascripts/prototype.js | 210 +++++++-- public/stylesheets/bootstrap-stripped.css | 24 + public/stylesheets/bootstrap-stripped.min.css | 2 +- public/stylesheets/main.styl | 33 +- test/test.components.js | 34 +- test/test.elements.js | 28 +- todo.txt | 1 + views/index.jade | 4 +- views/prototype.jade | 29 +- .../templates/{ => collections}/project.jade | 0 views/templates/{ => collections}/screen.jade | 0 .../heading.jade} | 6 +- .../input-checkbox.jade} | 2 +- .../input-radio.jade} | 2 +- .../input-text.jade} | 2 +- .../paragraph.jade} | 2 +- .../textarea.jade} | 2 +- views/templates/{ => layouts}/layout.jade | 0 .../article-element.jade} | 0 .../component.jade} | 0 .../form-element.jade} | 1 - views/templates/popovers/footer.jade | 10 + views/templates/popovers/heading.jade | 17 + views/templates/popovers/input-checkbox.jade | 6 + views/templates/popovers/input-radio.jade | 6 + views/templates/popovers/input-text.jade | 6 + views/templates/popovers/paragraph.jade | 6 + views/templates/popovers/textarea.jade | 6 + 37 files changed, 848 insertions(+), 104 deletions(-) create mode 100644 public/javascripts/.jshintrc create mode 100755 public/javascripts/bootstrap.js create mode 100644 todo.txt rename views/templates/{ => collections}/project.jade (100%) rename views/templates/{ => collections}/screen.jade (100%) rename views/templates/{heading-element.jade => elements/heading.jade} (50%) rename views/templates/{input-checkbox-element.jade => elements/input-checkbox.jade} (90%) rename views/templates/{input-radio-element.jade => elements/input-radio.jade} (90%) rename views/templates/{input-text-element.jade => elements/input-text.jade} (90%) rename views/templates/{paragraph-element.jade => elements/paragraph.jade} (50%) rename views/templates/{textarea-element.jade => elements/textarea.jade} (90%) rename views/templates/{ => layouts}/layout.jade (100%) rename views/templates/{article-element-list.jade => lists/article-element.jade} (100%) rename views/templates/{component-list.jade => lists/component.jade} (100%) rename views/templates/{form-element-list.jade => lists/form-element.jade} (99%) create mode 100644 views/templates/popovers/footer.jade create mode 100644 views/templates/popovers/heading.jade create mode 100644 views/templates/popovers/input-checkbox.jade create mode 100644 views/templates/popovers/input-radio.jade create mode 100644 views/templates/popovers/input-text.jade create mode 100644 views/templates/popovers/paragraph.jade create mode 100644 views/templates/popovers/textarea.jade diff --git a/.jshintignore b/.jshintignore index f2b05fe..595b0ef 100644 --- a/.jshintignore +++ b/.jshintignore @@ -2,4 +2,5 @@ node_modules public/javascripts/backbone.min.js public/javascripts/underscore.min.js public/javascripts/bootstrap.min.js +public/javascripts/bootstrap.js public/javascripts/json2.js diff --git a/lib/elements.js b/lib/elements.js index 9d2703c..a148bc1 100644 --- a/lib/elements.js +++ b/lib/elements.js @@ -5,7 +5,7 @@ const ALLOWED_FIELDS = { head: undefined, // > id of next element to form a linked list - next: undefined, + nextId: undefined, // input fields name: undefined, @@ -31,7 +31,7 @@ function getDefaultElement(req) { return { type: req.body.type, head: req.body.head, - next: req.body.next, + nextId: req.body.nextId, name: req.body.name, required: req.body.required || false, src: req.body.src, diff --git a/lib/scaffold.js b/lib/scaffold.js index 14b9da8..01cf6d3 100644 --- a/lib/scaffold.js +++ b/lib/scaffold.js @@ -115,11 +115,11 @@ exports.generate = function(parents, children, name, getDefault, callback = callback || utils.noop; crud.update(req, key, id, allowedFields, db, - function(err, project) { + function(err, object) { if (err) { callback(err); } else { - callback(null, project); + callback(null, object); } }); }; diff --git a/lib/utils.js b/lib/utils.js index 1a96c5e..034e4e5 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -15,22 +15,26 @@ exports.generateUniqueId = function(name, id) { return name.toString().toLowerCase().replace(ALPHANUM_MATCH, '_') + id; }; -/* Generically map object to req.body +/* Generically map curObject to req.body. If a property exists in curObject, + * req.body, and allowedFields, curObject's value is updated to equal + * req.body's value. If a property exists in req.body and allowedFields, but + * not in curObject, it is added to curObject with req.body's value. + * * Requires: web request, current object, allowed fields * Returns: The updated object if it exists, else false */ -exports.updateObject = function(req, currObject, allowedFields) { - if (typeof currObject !== 'object') { - currObject = JSON.parse(currObject); +exports.updateObject = function(req, curObject, allowedFields) { + if (typeof curObject !== 'object') { + curObject = JSON.parse(curObject); } - if (currObject) { - var setFields = function(currObject, from, allowedFields) { + if (curObject) { + var setFields = function(curObject, from, allowedFields) { var props = Object.getOwnPropertyNames(from); - var dest = currObject; + var dest = curObject; props.forEach(function(name) { - if (name in dest && name in allowedFields) { + if (name in allowedFields) { var destination = Object.getOwnPropertyDescriptor(from, name); if (destination) { @@ -39,10 +43,10 @@ exports.updateObject = function(req, currObject, allowedFields) { } }); - return currObject; + return curObject; }; - return setFields(currObject, req.body, allowedFields); + return setFields(curObject, req.body, allowedFields); } return false; diff --git a/public/javascripts/.jshintrc b/public/javascripts/.jshintrc new file mode 100644 index 0000000..da112e5 --- /dev/null +++ b/public/javascripts/.jshintrc @@ -0,0 +1,5 @@ +{ + "node": true, + "strict": false, + "esnext": true +} diff --git a/public/javascripts/bootstrap.js b/public/javascripts/bootstrap.js new file mode 100755 index 0000000..0917751 --- /dev/null +++ b/public/javascripts/bootstrap.js @@ -0,0 +1,442 @@ +/* =================================================== + * bootstrap-transition.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#transitions + * =================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + $(function () { + + "use strict"; // jshint ;_; + + + /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) + * ======================================================= */ + + $.support.transition = (function () { + + var transitionEnd = (function () { + + var el = document.createElement('bootstrap') + , transEndEventNames = { + 'WebkitTransition' : 'webkitTransitionEnd' + , 'MozTransition' : 'transitionend' + , 'OTransition' : 'oTransitionEnd' + , 'msTransition' : 'MSTransitionEnd' + , 'transition' : 'transitionend' + } + , name + + for (name in transEndEventNames){ + if (el.style[name] !== undefined) { + return transEndEventNames[name] + } + } + + }()) + + return transitionEnd && { + end: transitionEnd + } + + })() + + }) + +}(window.jQuery); +/* =========================================================== + * bootstrap-tooltip.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#tooltips + * Inspired by the original jQuery.tipsy by Jason Frame + * =========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* TOOLTIP PUBLIC CLASS DEFINITION + * =============================== */ + + var Tooltip = function (element, options) { + this.init('tooltip', element, options) + } + + Tooltip.prototype = { + + constructor: Tooltip + + , init: function (type, element, options) { + var eventIn + , eventOut + + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.enabled = true + + if (this.options.trigger != 'manual') { + eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus' + eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur' + this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this)) + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + , getOptions: function (options) { + options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data()) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay + , hide: options.delay + } + } + + return options + } + + , enter: function (e) { + var self = $(e.currentTarget)[this.type](this._options).data(this.type) + + if (!self.options.delay || !self.options.delay.show) return self.show() + + clearTimeout(this.timeout) + self.hoverState = 'in' + this.timeout = setTimeout(function() { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + , leave: function (e) { + var self = $(e.currentTarget)[this.type](this._options).data(this.type) + + if (this.timeout) clearTimeout(this.timeout) + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.hoverState = 'out' + this.timeout = setTimeout(function() { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + , show: function () { + var $tip + , inside + , pos + , actualWidth + , actualHeight + , placement + , tp + + if (this.hasContent() && this.enabled) { + $tip = this.tip() + this.setContent() + + if (this.options.animation) { + $tip.addClass('fade') + } + + placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + inside = /in/.test(placement) + + $tip + .remove() + .css({ top: 0, left: 0, display: 'block' }) + .data('element', this.$element) + .appendTo(inside ? this.$element : document.body) + + pos = this.getPosition(inside) + + actualWidth = $tip[0].offsetWidth + actualHeight = $tip[0].offsetHeight + + switch (inside ? placement.split(' ')[1] : placement) { + case 'bottom': + tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2} + break + case 'top': + tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2} + break + case 'left': + tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth} + break + case 'right': + tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width} + break + } + + $tip + .css(tp) + .addClass(placement) + .addClass('in') + } + } + + , isHTML: function(text) { + // html string detection logic adapted from jQuery + return typeof text != 'string' + || ( text.charAt(0) === "<" + && text.charAt( text.length - 1 ) === ">" + && text.length >= 3 + ) || /^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(text) + } + + , setContent: function () { + var $tip = this.tip() + , title = this.getTitle() + + $tip.find('.tooltip-inner')[this.isHTML(title) ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + , hide: function () { + var that = this + , $tip = this.tip() + + $tip.removeClass('in') + + function removeWithAnimation() { + var timeout = setTimeout(function () { + $tip.off($.support.transition.end).remove() + }, 500) + + $tip.one($.support.transition.end, function () { + clearTimeout(timeout) + $tip.remove() + }) + } + + $.support.transition && this.$tip.hasClass('fade') ? + removeWithAnimation() : + $tip.remove() + } + + , fixTitle: function () { + var $e = this.$element + if ($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 (inside) { + return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), { + width: this.$element[0].offsetWidth + , height: this.$element[0].offsetHeight + }) + } + + , getTitle: function () { + var title + , $e = this.$element + , o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + , tip: function () { + return this.$tip = this.$tip || $(this.options.template) + } + + , validate: function () { + if (!this.$element[0].parentNode) { + this.hide() + this.$element = null + this.options = null + } + } + + , enable: function () { + this.enabled = true + } + + , disable: function () { + this.enabled = false + } + + , toggleEnabled: function () { + this.enabled = !this.enabled + } + + , toggle: function () { + this[this.tip().hasClass('in') ? 'hide' : 'show']() + } + + } + + + /* TOOLTIP PLUGIN DEFINITION + * ========================= */ + + $.fn.tooltip = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('tooltip') + , options = typeof option == 'object' && option + if (!data) $this.data('tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.tooltip.Constructor = Tooltip + + $.fn.tooltip.defaults = { + animation: true + , placement: 'top' + , selector: false + , template: '
' + , trigger: 'hover' + , title: '' + , delay: 0 + } + +}(window.jQuery); + +/* =========================================================== + * bootstrap-popover.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#popovers + * =========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* POPOVER PUBLIC CLASS DEFINITION + * =============================== */ + + var Popover = function ( element, options ) { + this.init('popover', element, options) + } + + + /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js + ========================================== */ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, { + + constructor: Popover + + , setContent: function () { + var $tip = this.tip() + , title = this.getTitle() + , content = this.getContent() + , self = this + + $tip.find('.popover-title')[this.isHTML(title) ? 'html' : 'text'](title) + + if (this.isHTML(content)) { + $tip.find('.popover-content')['html'](content) + } else { + $tip.find('.popover-content > *')['text'](content) + } + + $tip.removeClass('fade top bottom left right in') + } + + , hasContent: function () { + return this.getTitle() || this.getContent() + } + + , getContent: function () { + var content + , $e = this.$element + , o = this.options + + content = $e.attr('data-content') + || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) + + return content + } + + , tip: function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + this.$tip.data('popover', this); + } + return this.$tip + } + }) + + + /* POPOVER PLUGIN DEFINITION + * ======================= */ + + $.fn.popover = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('popover') + , options = typeof option == 'object' && option + if (!data) $this.data('popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.popover.Constructor = Popover + + $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, { + placement: 'right' + , content: '' + , template: '

' + }) + +}(window.jQuery); diff --git a/public/javascripts/bootstrap.min.js b/public/javascripts/bootstrap.min.js index 1f4a35c..a9d82b4 100644 --- a/public/javascripts/bootstrap.min.js +++ b/public/javascripts/bootstrap.min.js @@ -1,7 +1,18 @@ -/** -* Bootstrap.js by @fat & @mdo -* plugins: bootstrap-transition.js, bootstrap-tooltip.js, bootstrap-popover.js -* Copyright 2012 Twitter, Inc. -* http://www.apache.org/licenses/LICENSE-2.0.txt -*/ -!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:'
',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:'

'})}(window.jQuery) \ No newline at end of file +/* =================================================== + * bootstrap-transition.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#transitions + * =================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */!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:'
',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:'

'})}(window.jQuery); \ No newline at end of file diff --git a/public/javascripts/example.js b/public/javascripts/example.js index 9f01d19..0c21ad3 100644 --- a/public/javascripts/example.js +++ b/public/javascripts/example.js @@ -23,7 +23,7 @@ // outerWidth/outerHeight = width/height with padding/border width: $sidebar.outerWidth(), height: $sidebar.outerHeight() - 70, - backgroundColor: 'rgba(0, 0, 0, 0.4)', + backgroundColor: 'rgba(0, 0, 0, 0.4)' }).appendTo('body'); $('
').css({ @@ -33,7 +33,7 @@ // outerWidth/outerHeight = width/height with padding/border width: $body.outerWidth() - $sidebar.outerWidth(), height: $sidebar.outerHeight(), - backgroundColor: 'rgba(0, 0, 0, 0.4)', + backgroundColor: 'rgba(0, 0, 0, 0.4)' }).appendTo('body'); $loginForm.children('a').popover({ diff --git a/public/javascripts/main.js b/public/javascripts/main.js index 140b689..e6519f4 100644 --- a/public/javascripts/main.js +++ b/public/javascripts/main.js @@ -288,7 +288,7 @@ function NapkinClient(window, document, $, data, undefined) { var that = this; projects.create({ - title: this.$projectInput.val().trim(), + title: this.$projectInput.val().trim() }, { error: tooltipErrorHandler(this.$projectInput), success: function(model) { @@ -303,7 +303,7 @@ function NapkinClient(window, document, $, data, undefined) { var that = this; screens.create({ - title: this.$screenInput.val().trim(), + title: this.$screenInput.val().trim() }, { error: tooltipErrorHandler(this.$screenInput), success: function(model) { diff --git a/public/javascripts/prototype.js b/public/javascripts/prototype.js index c3c5bff..953058a 100644 --- a/public/javascripts/prototype.js +++ b/public/javascripts/prototype.js @@ -83,7 +83,7 @@ function NapkinClient(window, document, $, data, undefined) { var $element = $target.siblings('.element').clone(); $element.removeClass('element') - .addClass('active-element'); + .addClass('live-element'); this.trigger('createElement', $element); } }); @@ -97,24 +97,18 @@ function NapkinClient(window, document, $, data, undefined) { $componentClicked: null, events: { - 'click [class^="span"]': 'recordActiveComponentClick', - 'click': 'blurComponent' + 'click [class^="span"]': 'recordComponentClick', + 'click .live-element': 'recordElementClick', + 'click': 'switchFocus' }, - selectComponent: function($component, model) { - $component.data('model', model); - $component.addClass(model.get('type') + '-container'); - $component.addClass('active'); + initialize: function() { + componentGroup.bind('add', this.addComponent, this); + componentGroup.bind('reset', this.addAllComponents, this); - // trigger a selectComponent event which the AppView will handle - this.$activeComponent = $component; - this.trigger('selectComponent', $component); - }, - - resetComponent: function($component, type) { - $component.removeClass(type + '-container'); - $component.removeClass('active'); - $component.empty(); + this.bind('closePopover', this.resetActiveElement, this); + this.bind('applyEdits', this.applyEdits, this); + this.bind('removeElement', this.removeElement, this); }, getDropOptions: function(layoutView) { @@ -167,11 +161,6 @@ function NapkinClient(window, document, $, data, undefined) { }; }, - initialize: function() { - componentGroup.bind('add', this.addComponent, this); - componentGroup.bind('reset', this.addAllComponents, this); - }, - render: function() { var that = this; this.$el.html(this.template({})); @@ -198,6 +187,22 @@ function NapkinClient(window, document, $, data, undefined) { return this; }, + selectComponent: function($component, model) { + $component.data('model', model); + $component.addClass(model.get('type') + '-container'); + $component.addClass('active'); + + // trigger a selectComponent event which the AppView will handle + this.$activeComponent = $component; + this.trigger('selectComponent', $component); + }, + + resetComponent: function($component, type) { + $component.removeClass(type + '-container'); + $component.removeClass('active'); + $component.empty(); + }, + addComponent: function(componentModel) { var layout = componentModel.get('layout'); var selector = '[data-position="' + layout.row + ':' + layout.col +'"]'; @@ -231,16 +236,25 @@ function NapkinClient(window, document, $, data, undefined) { }); }, - recordActiveComponentClick: function(event) { - var $component = $(event.currentTarget); - if ($component.is(this.$activeComponent)) { + recordComponentClick: function(event) { + var $target = $(event.currentTarget); + this.$componentClicked = $target; + + if ($target.is(this.$activeComponent)) { this.clickedActiveComponent = true; } + }, - this.$componentClicked = $component; + recordElementClick: function(event) { + var $target = $(event.currentTarget); + this.$elementClicked = $target; + + if ($target.is(this.$activeElement)) { + this.clickedActiveElement = true; + } }, - blurComponent: function(event) { + switchFocus: function(event) { // note that clickedActiveComponent will be set by the // recordComponentClick function due to event bubbling if (!this.clickedActiveComponent) { @@ -250,6 +264,7 @@ function NapkinClient(window, document, $, data, undefined) { if (this.$activeComponent) { this.$activeComponent.removeClass('active'); this.trigger('blurComponent'); + this.$activeComponent = null; } // if a component was clicked, put it in focus @@ -259,14 +274,45 @@ function NapkinClient(window, document, $, data, undefined) { this.trigger('selectComponent', $component); $component.addClass('active'); this.$activeComponent = $component; - } else { - this.$activeComponent = null; } } } + if (!this.clickedActiveElement) { + var $element = this.$elementClicked; + + // active element may not be defined if nothing is in focus + this.resetActiveElement(); + + // if an element was clicked, put it in focus + if ($element) { + this.setActiveElement($element); + } + } + + // reset click data this.clickedActiveComponent = false; + this.clickedActiveElement = false; this.$componentClicked = null; + this.$elementClicked = null; + }, + + setActiveElement: function($element) { + $element.popover('show'); + $element.addClass('active'); + this.$activeElement = $element; + }, + + resetActiveElement: function() { + var $element = this.$activeElement; + if ($element) { + $element.popover('hide'); + $element.removeClass('active'); + } + + this.$activeElement = null; + this.clickedActiveElement = false; + this.$elementClicked = null; }, createElement: function($element) { @@ -281,7 +327,7 @@ function NapkinClient(window, document, $, data, undefined) { var elementAttrs = { type: $element.data('type'), - next: null, + nextId: null, name: $element.data('name'), required: false, src: $element.data('src'), @@ -290,7 +336,7 @@ function NapkinClient(window, document, $, data, undefined) { }; // if there's no last element, this must be the head - var last = elementGroup.where({ next: null })[0]; + var last = elementGroup.where({ nextId: null })[0]; if (!last) { elementAttrs.head = true; } @@ -299,7 +345,7 @@ function NapkinClient(window, document, $, data, undefined) { // TODO: handle error success: function(model) { if (last) { - last.set('next', model.id); + last.set('nextId', model.id); last.save(); } } @@ -308,25 +354,93 @@ function NapkinClient(window, document, $, data, undefined) { addElement: function(element, $component) { var templateId = element.get('type') + '-element-template'; - var template = _.template($('#' + templateId).html()); - $(template(element.toJSON())).appendTo($component); + var elementTemplate = _.template($('#' + templateId).html()); + + templateId = element.get('type') + '-popover-template'; + var popoverTemplate = _.template($('#' + templateId).html()); + + var $element = $(elementTemplate(element.toJSON())); + var row = $component.data('position').row; + + var placement = 'right'; + if (row === 0) { + placement = 'bottom'; + } else if (row === 2) { + placement = 'top'; + } + + $element.appendTo($component); + $element.data('model', element); + + $element.popover({ + title: 'Edit Element', + trigger: 'manual', + placement: placement, + content: popoverTemplate(element.toJSON()) + }); }, addAllElements: function($component) { + // clear out any leftover elements + $component.empty(); + var elementGroup = $component.data('elementGroup'); var element = elementGroup.where({ head: true })[0]; // go through each element in the linked list while (element) { this.addElement(element, $component); - element = elementGroup.get(element.get('next')); + element = elementGroup.get(element.get('nextId')); } + }, + + applyEdits: function() { + var $element = this.$activeElement; + + }, + + removeElement: function() { + var $element = this.$activeElement; + this.resetActiveElement(); + var model = $element.data('model'); + + // $element.parent() is the component + var elementGroup = $element.parent().data('elementGroup'); + + var previous = elementGroup.where({ nextId: model.id })[0]; + var nextId = model.get('nextId'); + + model.destroy({ + success: function() { + if (previous) { + // rearrange links in the linked list + previous.set('nextId', nextId); + previous.save(); + } else { + // removing the first element, so reset the head + var next = elementGroup.get(nextId); + if (next) { // may also be the last element + next.set('head', true); + next.save(); + } + } + + $element.remove(); + } + }); } }); var AppView = Backbone.View.extend({ el: $('body'), + events: { + 'click .close-popover': 'closePopover', + 'click .popover-content .btn-primary': 'applyEdits', + 'click .popover-content .btn-danger': 'removeElement', + 'keydown': 'processKeyShortcuts' + }, + initialize: function() { this.componentListView = new ComponentListView(); $('#sidebar').append(this.componentListView.render().$el); @@ -359,6 +473,34 @@ function NapkinClient(window, document, $, data, undefined) { this.elementListView.unbind('addElement'); this.elementListView.remove(); } + }, + + // close popover when close popover link is clicked + closePopover: function(event) { + event.preventDefault(); + this.layoutView.trigger('closePopover'); + }, + + // apply edits to element when apply button is clicked + applyEdits: function(event) { + this.layoutView.trigger('applyEdits'); + }, + + // remove element when delete button is clicked + removeElement: function(event) { + this.layoutView.trigger('removeElement'); + }, + + // process key presses for shortcuts; e.g. backspace deletes an element + processKeyShortcuts: function(event) { + // ignore key presses in input fields + if ($('input[type="text"]:focus, textarea:focus').length === 0) { + // if the user hit backspace and has an element focused, remove it + if (event.which === 8 && this.layoutView.$activeElement) { + this.layoutView.trigger('removeElement'); + event.preventDefault(); + } + } } }); diff --git a/public/stylesheets/bootstrap-stripped.css b/public/stylesheets/bootstrap-stripped.css index 68f368d..45d6513 100644 --- a/public/stylesheets/bootstrap-stripped.css +++ b/public/stylesheets/bootstrap-stripped.css @@ -219,6 +219,24 @@ textarea { .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 { @@ -662,6 +680,12 @@ input[type="submit"].btn::-moz-focus-inner { .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; diff --git a/public/stylesheets/bootstrap-stripped.min.css b/public/stylesheets/bootstrap-stripped.min.css index b38d1af..e8bd7e9 100644 --- a/public/stylesheets/bootstrap-stripped.min.css +++ b/public/stylesheets/bootstrap-stripped.min.css @@ -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;} \ No newline at end of file +/*!* 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;} \ No newline at end of file diff --git a/public/stylesheets/main.styl b/public/stylesheets/main.styl index a2c8ce5..34e5634 100644 --- a/public/stylesheets/main.styl +++ b/public/stylesheets/main.styl @@ -295,10 +295,15 @@ header a float left width 190px -.active-element:hover +.live-element:hover, .live-element.active background rgba(78, 150, 0, 0.3) background #F7F3C5 +.live-element.active + padding 5px 10px + margin-left -10px + margin-right -10px + .elements .btn float right font-size 110% @@ -338,3 +343,29 @@ header a #prototype #content h2 margin-bottom 20px + +.popover-inner + width 260px + +.popover-content .form-actions + margin 10px -14px -14px + +.popover-content .form-actions .btn-primary + margin-right 10px + +.popover-content .form-actions i + margin-top 1px + margin-right 3px + +.close-popover + position absolute + right -5px + top -5px + padding 5px 5px 2px + background-color rgba(0, 0, 0, 0.9) + border-radius(15px) + +#level + width 45px + margin-left 10px + margin-top 7px diff --git a/test/test.components.js b/test/test.components.js index d29f02d..311848e 100644 --- a/test/test.components.js +++ b/test/test.components.js @@ -45,7 +45,10 @@ var componentReq = { }, body: { type: 'form', - layout: 'row1', + layout: { + row: 1, + col: 0 + }, action: '/' }, params: { @@ -60,7 +63,10 @@ var otherComponentReq = { }, body: { type: 'authentication', - layout: 'row1', + layout: { + row: 1, + col: 1 + }, action: '/auth' }, params: { @@ -107,7 +113,7 @@ describe('component', function() { components.add(req, db, function(err, component) { component.type.should.equal(req.body.type); - component.layout.should.equal(req.body.layout); + component.layout.should.eql(req.body.layout); component.action.should.equal(req.body.action); done(); }); @@ -121,7 +127,7 @@ describe('component', function() { setTimeout(function() { components.get(req, db, 2, function(err, component) { component.type.should.equal(req.body.type); - component.layout.should.equal(req.body.layout); + component.layout.should.eql(req.body.layout); component.action.should.equal(req.body.action); done(); }); @@ -136,12 +142,12 @@ describe('component', function() { components.list(req, db, function(errList, componentList) { componentList[0].type.should.equal(req.body.type); - componentList[0].layout.should.equal(req.body.layout); + componentList[0].layout.should.eql(req.body.layout); componentList[0].action.should.equal(req.body.action); req = otherComponentReq; componentList[1].type.should.equal(req.body.type); - componentList[1].layout.should.equal(req.body.layout); + componentList[1].layout.should.eql(req.body.layout); componentList[1].action.should.equal(req.body.action); done(); }); @@ -155,7 +161,7 @@ describe('component', function() { it('returns a specific component', function(done) { components.get(req, db, 1, function(err, component) { component.type.should.equal(req.body.type); - component.layout.should.equal(req.body.layout); + component.layout.should.eql(req.body.layout); component.action.should.equal(req.body.action); done(); }); @@ -174,21 +180,27 @@ describe('component', function() { var req = componentReq; it('updates a component', function(done) { - req.body.layout = 'row2'; + req.body.layout = { + row: 2, + col: 1, + }; components.update(req, db, 1, function(err, component) { - component.layout.should.equal(req.body.layout); + component.layout.should.eql(req.body.layout); done(); }); }); it('accepts an empty callback', function(done) { - req.body.layout = 'row3'; + req.body.layout = { + row: 2, + col: 2 + }; components.update(req, db, 1); // wait 10ms for db transaction to complete setTimeout(function() { components.get(req, db, 1, function(err, component) { - component.layout.should.equal(req.body.layout); + component.layout.should.eql(req.body.layout); done(); }); }, 10); diff --git a/test/test.elements.js b/test/test.elements.js index 0110c89..655cd61 100644 --- a/test/test.elements.js +++ b/test/test.elements.js @@ -60,7 +60,8 @@ var elementReq = { }, body: { type: 'input_text', - layout: 'row1', + head: true, + nextId: 1, required: true, src: '' }, @@ -77,7 +78,7 @@ var otherElementReq = { }, body: { type: 'input_radio', - layout: 'col1', + nextId: null, required: true, src: '' }, @@ -112,7 +113,8 @@ describe('element', function() { var req = elementReq; elements.add(req, db, function(err, element) { element.type.should.equal(req.body.type); - element.layout.should.equal(req.body.layout); + element.head.should.equal(req.body.head); + element.nextId.should.equal(req.body.nextId); element.required.should.equal(req.body.required); element.src.should.equal(req.body.src); done(); @@ -127,7 +129,8 @@ describe('element', function() { setTimeout(function() { elements.get(req, db, 2, function(err, element) { element.type.should.equal(req.body.type); - element.layout.should.equal(req.body.layout); + should.not.exist(element.head); + should.not.exist(element.nextId); element.required.should.equal(req.body.required); element.src.should.equal(req.body.src); done(); @@ -143,13 +146,15 @@ describe('element', function() { elements.list(req, db, function(errList, elementList) { elementList[0].type.should.equal(req.body.type); - elementList[0].layout.should.equal(req.body.layout); + elementList[0].head.should.equal(req.body.head); + elementList[0].nextId.should.equal(req.body.nextId); elementList[0].required.should.equal(req.body.required); elementList[0].src.should.equal(req.body.src); req = otherElementReq; elementList[1].type.should.equal(req.body.type); - elementList[1].layout.should.equal(req.body.layout); + should.not.exist(elementList[1].head); + should.not.exist(elementList[1].nextId); elementList[1].required.should.equal(req.body.required); elementList[1].src.should.equal(req.body.src); done(); @@ -164,7 +169,8 @@ describe('element', function() { it('returns a specific element', function(done) { elements.get(req, db, 1, function(err, element) { element.type.should.equal(req.body.type); - element.layout.should.equal(req.body.layout); + element.head.should.equal(req.body.head); + element.nextId.should.equal(req.body.nextId); element.required.should.equal(req.body.required); element.src.should.equal(req.body.src); done(); @@ -184,21 +190,21 @@ describe('element', function() { var req = elementReq; it('updates a specific element', function(done) { - req.body.layout = 'row2'; + req.body.nextId = 2; elements.update(req, db, 1, function(err, element) { - element.layout.should.equal(req.body.layout); + element.nextId.should.equal(req.body.nextId); done(); }); }); it('accepts an empty callback', function(done) { - req.body.layout = 'row3'; + req.body.nextId = 3; elements.update(req, db, 1); // wait 10ms for db transaction to complete setTimeout(function() { elements.get(req, db, 1, function(err, element) { - element.layout.should.equal(req.body.layout); + element.nextId.should.equal(req.body.nextId); done(); }); }, 10); diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..ab003e5 --- /dev/null +++ b/todo.txt @@ -0,0 +1 @@ +- Restructure files in new folders before commit diff --git a/views/index.jade b/views/index.jade index b1ed915..f9b5f44 100644 --- a/views/index.jade +++ b/views/index.jade @@ -7,8 +7,8 @@ block sidebar block content !=partial('forms/add-screen') - !=partial('templates/project') - !=partial('templates/screen') + !=partial('templates/collections/project') + !=partial('templates/collections/screen') block scripts script(src='/javascripts/json2.js') diff --git a/views/prototype.jade b/views/prototype.jade index fd77404..0ca3a52 100644 --- a/views/prototype.jade +++ b/views/prototype.jade @@ -14,15 +14,24 @@ block scripts }); }); - != partial('templates/layout') - != partial('templates/component-list') - != partial('templates/article-element-list') - != partial('templates/form-element-list') + // layouts and lists + != partial('templates/layouts/layout') + != partial('templates/lists/component') + != partial('templates/lists/article-element') + != partial('templates/lists/form-element') - != partial('templates/heading-element') - != partial('templates/paragraph-element') + // article elements and popovers + != partial('templates/elements/heading') + != partial('templates/popovers/heading') + != partial('templates/elements/paragraph') + != partial('templates/popovers/paragraph') - != partial('templates/input-text-element') - != partial('templates/input-radio-element') - != partial('templates/input-checkbox-element') - != partial('templates/textarea-element') + // form elements and popovers + != partial('templates/elements/input-text') + != partial('templates/popovers/input-text') + != partial('templates/elements/input-radio') + != partial('templates/popovers/input-radio') + != partial('templates/elements/input-checkbox') + != partial('templates/popovers/input-checkbox') + != partial('templates/elements/textarea') + != partial('templates/popovers/textarea') diff --git a/views/templates/project.jade b/views/templates/collections/project.jade similarity index 100% rename from views/templates/project.jade rename to views/templates/collections/project.jade diff --git a/views/templates/screen.jade b/views/templates/collections/screen.jade similarity index 100% rename from views/templates/screen.jade rename to views/templates/collections/screen.jade diff --git a/views/templates/heading-element.jade b/views/templates/elements/heading.jade similarity index 50% rename from views/templates/heading-element.jade rename to views/templates/elements/heading.jade index 434a2c0..8c4828a 100644 --- a/views/templates/heading-element.jade +++ b/views/templates/elements/heading.jade @@ -1,8 +1,8 @@ script#heading-element-template(type='text/template') <% if (level === 1) { %> - h1.active-element(data-type='heading') <%- text %> + h1.live-element(data-type='heading') <%- text %> <% } else if (level === 2) { %> - h2.active-element(data-type='heading') <%- text %> + h2.live-element(data-type='heading') <%- text %> <% } else if (level === 3) { %> - h3.active-element(data-type='heading') <%- text %> + h3.live-element(data-type='heading') <%- text %> <% } %> diff --git a/views/templates/input-checkbox-element.jade b/views/templates/elements/input-checkbox.jade similarity index 90% rename from views/templates/input-checkbox-element.jade rename to views/templates/elements/input-checkbox.jade index dfad4d6..51ebcd4 100644 --- a/views/templates/input-checkbox-element.jade +++ b/views/templates/elements/input-checkbox.jade @@ -1,5 +1,5 @@ script#input-checkbox-element-template(type='text/template') // TODO: make names unique and id-ize them for inputs - .active-element.field + .live-element.field label(for!='<%- name %>') <%- name %>: input(type='checkbox', name!='<%- name %>', id!='<%- name %>') diff --git a/views/templates/input-radio-element.jade b/views/templates/elements/input-radio.jade similarity index 90% rename from views/templates/input-radio-element.jade rename to views/templates/elements/input-radio.jade index 8ad1a10..be002d9 100644 --- a/views/templates/input-radio-element.jade +++ b/views/templates/elements/input-radio.jade @@ -1,5 +1,5 @@ script#input-radio-element-template(type='text/template') // TODO: make names unique and id-ize them for inputs - .active-element.field + .live-element.field label(for!='<%- name %>') <%- name %>: input(type='radio', name!='<%- name %>', id!='<%- name %>') diff --git a/views/templates/input-text-element.jade b/views/templates/elements/input-text.jade similarity index 90% rename from views/templates/input-text-element.jade rename to views/templates/elements/input-text.jade index 6bae35f..74ce8ac 100644 --- a/views/templates/input-text-element.jade +++ b/views/templates/elements/input-text.jade @@ -1,5 +1,5 @@ script#input-text-element-template(type='text/template') // TODO: make names unique and id-ize them for inputs - .active-element.field + .live-element.field label(for!='<%- name %>') <%- name %>: input(type='text', name!='<%- name %>', id!='<%- name %>') diff --git a/views/templates/paragraph-element.jade b/views/templates/elements/paragraph.jade similarity index 50% rename from views/templates/paragraph-element.jade rename to views/templates/elements/paragraph.jade index e9e8f75..6aaf10a 100644 --- a/views/templates/paragraph-element.jade +++ b/views/templates/elements/paragraph.jade @@ -1,2 +1,2 @@ script#paragraph-element-template(type='text/template') - p.active-element(data-type='paragraph') <%- text %> + p.live-element(data-type='paragraph') <%- text %> diff --git a/views/templates/textarea-element.jade b/views/templates/elements/textarea.jade similarity index 90% rename from views/templates/textarea-element.jade rename to views/templates/elements/textarea.jade index 245eb01..c7a1265 100644 --- a/views/templates/textarea-element.jade +++ b/views/templates/elements/textarea.jade @@ -1,5 +1,5 @@ script#textarea-element-template(type='text/template') // TODO: make names unique and id-ize them for textarea - .active-element.field + .live-element.field label(for!='<%- name %>') <%- name %>: textarea(rows='8', cols='20', name!='<%- name %>', id!='<%- name %>') diff --git a/views/templates/layout.jade b/views/templates/layouts/layout.jade similarity index 100% rename from views/templates/layout.jade rename to views/templates/layouts/layout.jade diff --git a/views/templates/article-element-list.jade b/views/templates/lists/article-element.jade similarity index 100% rename from views/templates/article-element-list.jade rename to views/templates/lists/article-element.jade diff --git a/views/templates/component-list.jade b/views/templates/lists/component.jade similarity index 100% rename from views/templates/component-list.jade rename to views/templates/lists/component.jade diff --git a/views/templates/form-element-list.jade b/views/templates/lists/form-element.jade similarity index 99% rename from views/templates/form-element-list.jade rename to views/templates/lists/form-element.jade index 49beb7a..38feb15 100644 --- a/views/templates/form-element-list.jade +++ b/views/templates/lists/form-element.jade @@ -22,4 +22,3 @@ script#form-element-list(type='text/template') label(for='textarea') Textarea: textarea(rows='8', cols='20', name='textarea') a.btn.btn-success + - diff --git a/views/templates/popovers/footer.jade b/views/templates/popovers/footer.jade new file mode 100644 index 0000000..5f334bb --- /dev/null +++ b/views/templates/popovers/footer.jade @@ -0,0 +1,10 @@ +.form-actions + a.btn.btn-primary + i.icon-refresh.icon-white + | Apply + a.btn.btn-danger + i.icon-trash.icon-white + | Delete + +a.close-popover(href='#close') + i.icon-remove.icon-white diff --git a/views/templates/popovers/heading.jade b/views/templates/popovers/heading.jade new file mode 100644 index 0000000..6fcc8b0 --- /dev/null +++ b/views/templates/popovers/heading.jade @@ -0,0 +1,17 @@ +script#heading-popover-template(type='text/template') + .field + label(for='text') Text: + input#text(name='text', type='text', value!='<%- text %>') + + .field + label(for='level') Level: + select#level(name='level') + <% for(var i = 1; i <= 6; i++) { %> + <% if(i === level) { %> + option(value!='<%- i %>', selected='true') <%- i %> + <% } else { %> + option(value!='<%- i %>') <%- i %> + <% } %> + <% } %> + + != partial('templates/popovers/footer') diff --git a/views/templates/popovers/input-checkbox.jade b/views/templates/popovers/input-checkbox.jade new file mode 100644 index 0000000..c3272c1 --- /dev/null +++ b/views/templates/popovers/input-checkbox.jade @@ -0,0 +1,6 @@ +script#input-checkbox-popover-template(type='text/template') + .field + label(for='name') Name: + input#name(name='name', type='text', value!='<%- name %>') + + != partial('templates/popovers/footer') diff --git a/views/templates/popovers/input-radio.jade b/views/templates/popovers/input-radio.jade new file mode 100644 index 0000000..6cfba7b --- /dev/null +++ b/views/templates/popovers/input-radio.jade @@ -0,0 +1,6 @@ +script#input-radio-popover-template(type='text/template') + .field + label(for='name') Name: + input#name(name='name', type='text', value!='<%- name %>') + + != partial('templates/popovers/footer') diff --git a/views/templates/popovers/input-text.jade b/views/templates/popovers/input-text.jade new file mode 100644 index 0000000..9c0ca5e --- /dev/null +++ b/views/templates/popovers/input-text.jade @@ -0,0 +1,6 @@ +script#input-text-popover-template(type='text/template') + .field + label(for='name') Name: + input#name(name='name', type='text', value!='<%- name %>') + + != partial('templates/popovers/footer') diff --git a/views/templates/popovers/paragraph.jade b/views/templates/popovers/paragraph.jade new file mode 100644 index 0000000..1948abf --- /dev/null +++ b/views/templates/popovers/paragraph.jade @@ -0,0 +1,6 @@ +script#paragraph-popover-template(type='text/template') + .field + label(for='text') Text: + textarea#text(name='text', type='text', value!='<%- text %>') + + != partial('templates/popovers/footer') diff --git a/views/templates/popovers/textarea.jade b/views/templates/popovers/textarea.jade new file mode 100644 index 0000000..5e7c4bd --- /dev/null +++ b/views/templates/popovers/textarea.jade @@ -0,0 +1,6 @@ +script#textarea-popover-template(type='text/template') + .field + label(for='name') Name: + input#name(name='name', type='text', value!='<%- name %>') + + != partial('templates/popovers/footer')