styling, tagging

This commit is contained in:
Tyrel Souza 2014-04-22 15:22:36 -04:00
parent 466141adad
commit 67ed4565d4
15 changed files with 734 additions and 98 deletions

View File

@ -21,6 +21,7 @@ gem 'unicorn'
gem 'bootstrap-sass', '<3.1'
gem 'devise'
gem 'jbuilder'
gem 'bootstrap-tagsinput-rails'
group :development do

View File

@ -50,6 +50,8 @@ GEM
debug_inspector (>= 0.0.1)
bootstrap-sass (3.0.3.0)
sass (~> 3.2)
bootstrap-tagsinput-rails (0.3.2.0)
railties (>= 3.1)
builder (3.2.2)
capybara (2.1.0)
mime-types (>= 1.16)
@ -229,6 +231,7 @@ DEPENDENCIES
better_errors
binding_of_caller
bootstrap-sass (< 3.1)
bootstrap-tagsinput-rails
capybara-webkit (>= 1.0.0)
coffee-rails
database_cleaner

View File

@ -12,4 +12,5 @@
//
//= require jquery
//= require jquery_ujs
//= require bootstrap-tagsinput
//= require_tree .

View File

@ -3,5 +3,7 @@
///////////////////////////////////////////////////////////////////////////////
@import 'bootstrap';
@import 'bootstrap-tagsinput.css';
@import 'campaign';
///////////////////////////////////////////////////////////////////////////////

View File

@ -56,4 +56,27 @@ select {
span {
padding-left: 2em;
}
}
span.list-item {
display:block;
}
}
span.tag.label.label-info {
display: block;
margin-bottom:1px;
.tag-item {
width:90%;
white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
white-space: pre-wrap; /* css-3 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
word-break: break-all;
white-space: normal;
}
}
div.bootstrap-tagsinput {
width:100%;
}

View File

@ -24,22 +24,29 @@
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-5">
<div class="col-md-4 col-md-offset-1">
<%= frm.input :advertisername,
label: 'Advertiser Name',
hint: "The name of the company." %>
<%= frm.input :listingcode,
label: 'Listing Code',
hint: "This should be the same as Intake Form's Form Hash." %>
<%= frm.input :clientid,
label: 'Client ID',
hint: 'An identifier provided by BetterVideo.' %>
<%= frm.input :listingcode,
label: 'Listing Code',
hint: "This should be the same as Intake Form's Form Hash." %>
<%= frm.input :description,
label: 'Description',
as: 'text',
hint: "Used for script research." %>
<%= frm.input :productsandservices,
label: 'Products & Services',
hint: "Used in script and video research." %>
<%= frm.input :websiteurl,
label: 'Website URL',
hint: "Used for script research and may be used in the video if they are including websites in the video graphics." %>
<%= frm.input :vpa,
<%= frm.input :vpa,
label: 'Client Service Rep',
hint: "This is the full name of the client services representative, as entered in the BetteVideo system associated with this order. This feature is designed for sales reps that are well known to the system. This feature is typically used for automated communication." %>
@ -79,16 +86,11 @@
label: 'Company Colors',
hint: "Comma Separated.",
placeholder: "ex: red, white, blue" %>
<%= frm.input :description,
label: 'Description',
as: 'text',
hint: "Used for script research." %>
<%= frm.input :facebookurl,
label: 'Facebook URL',
hint: "Used for script and photo research." %>
<%= frm.input :productsandservices,
label: 'Products & Services',
hint: "Used in script and video research." %>
<%= frm.input :targetaudience,
label: 'Target Audience',
hint: "Used in script and video research." %>
@ -97,7 +99,7 @@
hint: "Toll-free phone number of business. Used in video creation." %>
</div>
<div class="col-md-5">
<div class="col-md-3">
<div class="videolisting">
<%= frm.simple_fields_for(:videolistings, html: {class: 'form-horizontal'}) do |vidlist| %>
<div class="<%= cycle('odd','even') %>">
@ -130,15 +132,14 @@
label: 'Notes',
hint: "Notes and/or instructions sent in this field are used by the editors to aid in production of the video." %>
<%= vidlist.input :asseturls,
as: 'text',
label: 'Asset Urls',
placeholder: "ex: http://www.company.com/logo.png, http://www.company.com/background.png",
hint: "A comma separated list of URLs of any assets." %>
hint: "A list of all file assets.",
input_html: {data: {role: "tagsinput" }}%>
</div>
<% end %>
</div>
</div>
<div class="col-md-1"></div>
</div>

View File

@ -33,6 +33,9 @@
</span>
<span class="videolisting-item">
<strong>asseturls</strong>
<span><%= videolisting.asseturls %></span>
<% videolisting.asseturls_array.each do |asset| %>
<span class="list-item"><%= asset[:asseturl] %></span>
<% end %>
</span>
</li>

View File

@ -2,99 +2,115 @@
<%= link_to 'Back', campaigns_path %>
<div class="col-md-8">
<h1>
<%= @campaign.advertisername %>
</h1>
<p id="notice"><%= notice %></p>
<div class="col-md-12">
<h1>
<%= @campaign.advertisername %>
</h1>
<p id="notice"><%= notice %></p>
</div>
</div>
<div class="col-md-8">
<div class="col-md-2"></div>
<div class="col-md-4">
<div class="col-md-4 col-md-offset-1 col-xs-6">
<span class="campaign-item">
<strong>Clientid</strong>
<span><%= @campaign.clientid %></span>
</span>
<span class="campaign-item">
<strong>Listingcode</strong>
<strong>Listing Code</strong>
<span><%= @campaign.listingcode %></span>
</span>
<span class="campaign-item">
<strong>Billingcode</strong>
<span><%= @campaign.billingcode %></span>
<strong>Client Id</strong>
<span><%= @campaign.clientid %></span>
</span>
<span class="campaign-item">
<strong>Address1</strong>
<span><%= @campaign.address %></span>
</span>
<span class="campaign-item">
<strong>City</strong>
<span><%= @campaign.city %></span>
</span>
<span class="campaign-item">
<strong>State</strong>
<span><%= @campaign.state %></span>
</span>
<span class="campaign-item">
<strong>Zip</strong>
<span><%= @campaign.zip %></span>
</span>
<span class="campaign-item">
<strong>Email Address</strong>
<span><%= @campaign.emailaddress %></span>
</span>
<span class="campaign-item">
<strong>Customer First Name</strong>
<span><%= @campaign.customerfirstname %></span>
</span>
<span class="campaign-item">
<strong>Customer Last Name</strong>
<span><%= @campaign.customerlastname %></span>
</span>
<span class="campaign-item">
<strong>Businessphone</strong>
<span><%= @campaign.businessphone %></span>
</span>
<span class="campaign-item">
<strong>Description</strong>
<span><%= @campaign.description %></span>
</span>
<span class="campaign-item">
<strong>Contactphone</strong>
<span><%= @campaign.contactphone %></span>
<strong>Products & Services</strong>
<span><%= @campaign.productsandservices %></span>
</span>
<span class="campaign-item">
<strong>Websiteurl</strong>
<span><%= @campaign.websiteurl %></span>
</span>
<span class="campaign-item">
<strong>Client Services Rep</strong>
<span><%= @campaign.vpa %></span>
</span>
<span class="campaign-item">
<strong>Billing Code</strong>
<span><%= @campaign.billingcode %></span>
</span>
<span class="campaign-item">
<strong>Email Address</strong>
<span><%= @campaign.emailaddress %></span>
</span>
<span class="campaign-item">
<strong>Business Phone</strong>
<span><%= number_to_phone @campaign.businessphone %></span>
</span>
<span class="campaign-item">
<strong>Contact Phone</strong>
<span><%= number_to_phone @campaign.contactphone %></span>
</span>
<span class="campaign-item">
<strong>Customer First Name</strong>
<span><%= @campaign.customerfirstname %></span>
</span>
<span class="campaign-item">
<strong>Customer Last Name</strong>
<span><%= @campaign.customerlastname %></span>
</span>
<span class="campaign-item">
<strong>Address</strong>
<span><%= @campaign.address %></span>
</span>
<span class="campaign-item">
<strong>City</strong>
<span><%= @campaign.city %></span>
</span>
<span class="campaign-item">
<strong>State</strong>
<span><%= @campaign.state %></span>
</span>
<span class="campaign-item">
<strong>Zip</strong>
<span><%= @campaign.zip %></span>
</span>
<span class="campaign-item">
<strong>Awards</strong>
<span><%= @campaign.awards %></span>
</span>
<span class="campaign-item">
<strong>Background</strong>
<span><%= @campaign.background %></span>
</span>
<span class="campaign-item">
<strong>Categories</strong>
<span><%= @campaign.categories %></span>
</span>
<span class="campaign-item">
<strong>Company Colors</strong>
<span><%= @campaign.companycolors %></span>
</span>
<span class="campaign-item">
<strong>Facebook URL</strong>
<span><%= @campaign.facebookurl %></span>
</span>
<span class="campaign-item">
<strong>Target Audience</strong>
<span><%= @campaign.targetaudience %></span>
</span>
<span class="campaign-item">
<strong>Tollfree Phone</strong>
<span><%= @campaign.tollfreephone %></span>
</span>
</div>
<div class="col-md-4">
<div class="col-md-4 col-xs-6">
<ul>
<% @campaign.videolistings.each do |videolisting| %>
<%= render 'videolisting', videolisting: videolisting %>
<% end %>
<% if @campaign.videolistings.empty? %>
<li>No Video Listings for this Campaign.</li>
<% end %>
</ul>
<% @campaign.videolistings.each do |videolisting| %>
<%= render 'videolisting', videolisting: videolisting %>
<% end %>
<% if @campaign.videolistings.empty? %>
<li>No Video Listings for this Campaign.</li>
<% end %>
</ul>
</div>
<div class="col-md-2"></div>
</div>

View File

@ -9,9 +9,9 @@
</head>
<body class="<%= body_class %>">
<div class="col-md-12">
<%= render 'layouts/header' %>
<%= render 'flashes' -%>
<%= yield %>
<%= render 'flashes' -%>
<%= render 'layouts/header' %>
<%= yield %>
</div>
<%= render 'javascript' %>
</body>

View File

@ -0,0 +1,80 @@
angular.module('bootstrap-tagsinput', [])
.directive('bootstrapTagsinput', [function() {
function getItemProperty(scope, property) {
if (!property)
return undefined;
if (angular.isFunction(scope.$parent[property]))
return scope.$parent[property];
return function(item) {
return item[property];
};
}
return {
restrict: 'EA',
scope: {
model: '=ngModel'
},
template: '<select multiple></select>',
replace: false,
link: function(scope, element, attrs) {
$(function() {
if (!angular.isArray(scope.model))
scope.model = [];
var select = $('select', element);
select.tagsinput({
typeahead : {
source : angular.isFunction(scope.$parent[attrs.typeaheadSource]) ? scope.$parent[attrs.typeaheadSource] : null
},
itemValue: getItemProperty(scope, attrs.itemvalue),
itemText : getItemProperty(scope, attrs.itemtext),
tagClass : angular.isFunction(scope.$parent[attrs.tagclass]) ? scope.$parent[attrs.tagclass] : function(item) { return attrs.tagclass; }
});
for (var i = 0; i < scope.model.length; i++) {
select.tagsinput('add', scope.model[i]);
}
select.on('itemAdded', function(event) {
if (scope.model.indexOf(event.item) === -1)
scope.model.push(event.item);
});
select.on('itemRemoved', function(event) {
var idx = scope.model.indexOf(event.item);
if (idx !== -1)
scope.model.splice(idx, 1);
});
// create a shallow copy of model's current state, needed to determine
// diff when model changes
var prev = scope.model.slice();
scope.$watch("model", function() {
var added = scope.model.filter(function(i) {return prev.indexOf(i) === -1;}),
removed = prev.filter(function(i) {return scope.model.indexOf(i) === -1;}),
i;
prev = scope.model.slice();
// Remove tags no longer in binded model
for (i = 0; i < removed.length; i++) {
select.tagsinput('remove', removed[i]);
}
// Refresh remaining tags
select.tagsinput('refresh');
// Add new items in model as tags
for (i = 0; i < added.length; i++) {
select.tagsinput('add', added[i]);
}
}, true);
});
}
};
}]);

View File

@ -0,0 +1,405 @@
(function ($) {
"use strict";
var defaultOptions = {
tagClass: function(item) {
return 'label label-info';
},
itemValue: function(item) {
return item ? item.toString() : item;
},
itemText: function(item) {
return this.itemValue(item);
},
freeInput : true
};
function TagsInput(element, options) {
this.itemsArray = [];
this.$element = $(element);
this.$element.hide();
this.isSelect = (element.tagName === 'SELECT');
this.multiple = (this.isSelect && element.hasAttribute('multiple'));
this.objectItems = options && options.itemValue;
this.$container = $('<div class="bootstrap-tagsinput"></div>');
this.$input = $('<input size="40" type="text" />').appendTo(this.$container);
this.$element.after(this.$container);
this.build(options);
}
TagsInput.prototype = {
constructor: TagsInput,
add: function(item, dontPushVal) {
var self = this;
// Ignore falsey values, except false
if (item !== false && !item)
return;
// Throw an error when trying to add an object while the itemValue option was not set
if (typeof item === "object" && !self.objectItems)
throw("Can't add objects when itemValue option is not set");
// Ignore strings only containg whitespace
if (item.toString().match(/^\s*$/))
return;
// If SELECT but not multiple, remove current tag
if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
self.remove(self.itemsArray[0]);
if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
var items = item.split(',');
if (items.length > 1) {
for (var i = 0; i < items.length; i++) {
this.add(items[i], true);
}
if (!dontPushVal)
self.pushVal();
return;
}
}
// Ignore items allready added
var itemValue = self.options.itemValue(item),
itemText = self.options.itemText(item),
tagClass = self.options.tagClass(item);
if ($.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0])
return;
// register item in internal array and map
self.itemsArray.push(item);
// add a tag element
var $tag = $('<span class="tag ' + htmlEncode(tagClass) + '"><span class="tag-item">' + htmlEncode(itemText) + '</span><span class="remove" data-role="remove"></span></span>');
$tag.data('item', item);
self.$input.before($tag);
// add <option /> if item represents a value not present in one of the <select />'s options
if (self.isSelect && !$('option[value="' + escape(itemValue) + '"]')[0]) {
var $option = $('<option selected>' + htmlEncode(itemText) + '</option>');
$option.data('item', item);
$option.attr('value', itemValue);
self.$element.append($option);
}
if (!dontPushVal)
self.pushVal();
self.$element.trigger($.Event('itemAdded', { item: item }));
},
remove: function(item, dontPushVal) {
var self = this;
if (self.objectItems) {
if (typeof item === "object")
item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } )[0];
else
item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } )[0];
}
if (item) {
$('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
$('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
self.itemsArray.splice(self.itemsArray.indexOf(item), 1);
}
if (!dontPushVal)
self.pushVal();
self.$element.trigger($.Event('itemRemoved', { item: item }));
},
removeAll: function() {
var self = this;
$('.tag', self.$container).remove();
$('option', self.$element).remove();
while(self.itemsArray.length > 0)
self.itemsArray.pop();
self.pushVal();
},
refresh: function() {
var self = this;
$('.tag', self.$container).each(function() {
var $tag = $(this),
item = $tag.data('item'),
itemValue = self.options.itemValue(item),
itemText = self.options.itemText(item),
tagClass = self.options.tagClass(item);
// Update tag's class and inner text
$tag.attr('class', null);
$tag.addClass('tag ' + htmlEncode(tagClass));
$tag.contents().filter(function() {
return this.nodeType == 3;
})[0].nodeValue = htmlEncode(itemText);
if (self.isSelect) {
var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
option.attr('value', itemValue);
}
});
},
// Returns the items added as tags
items: function() {
return this.itemsArray;
},
// Assembly value by retrieving the value of each item, and set it on the element.
pushVal: function() {
var self = this,
val = $.map(self.items(), function(item) {
return self.options.itemValue(item).toString();
});
self.$element.val(val, true).trigger('change');
},
build: function(options) {
var self = this;
self.options = $.extend({}, defaultOptions, options);
var typeahead = self.options.typeahead || {};
// When itemValue is set, freeInput should always be false
if (self.objectItems)
self.options.freeInput = false;
makeOptionItemFunction(self.options, 'itemValue');
makeOptionItemFunction(self.options, 'itemText');
makeOptionItemFunction(self.options, 'tagClass');
// for backwards compatibility, self.options.source is deprecated
if (self.options.source)
typeahead.source = self.options.source;
if (typeahead.source && $.fn.typeahead) {
makeOptionFunction(typeahead, 'source');
self.$input.typeahead({
source: function (query, process) {
function processItems(items) {
var texts = [];
for (var i = 0; i < items.length; i++) {
var text = self.options.itemText(items[i]);
map[text] = items[i];
texts.push(text);
}
process(texts);
}
this.map = {};
var map = this.map,
data = typeahead.source(query);
if ($.isFunction(data.success)) {
// support for Angular promises
data.success(processItems);
} else {
// support for functions and jquery promises
$.when(data)
.then(processItems);
}
},
updater: function (text) {
self.add(this.map[text]);
},
matcher: function (text) {
return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
},
sorter: function (texts) {
return texts.sort();
},
highlighter: function (text) {
var regex = new RegExp( '(' + this.query + ')', 'gi' );
return text.replace( regex, "<strong>$1</strong>" );
}
});
}
self.$container.on('click', $.proxy(function(event) {
self.$input.focus();
}, self));
self.$container.on('keydown', 'input', $.proxy(function(event) {
var $input = $(event.target);
switch (event.which) {
// BACKSPACE
case 8:
if (doGetCaretPosition($input[0]) === 0) {
var prev = $input.prev();
if (prev) {
self.remove(prev.data('item'));
}
}
break;
// DELETE
case 46:
if (doGetCaretPosition($input[0]) === 0) {
var next = $input.next();
if (next) {
self.remove(next.data('item'));
}
}
break;
// LEFT ARROW
case 37:
// Try to move the input before the previous tag
var $prevTag = $input.prev();
if ($input.val().length === 0 && $prevTag[0]) {
$prevTag.before($input);
$input.focus();
}
break;
// LEFT ARROW
case 39:
// Try to move the input before the previous tag
var $nextTag = $input.next();
if ($input.val().length === 0 && $nextTag[0]) {
$nextTag.after($input);
$input.focus();
}
break;
// ENTER
case 13:
if (self.options.freeInput) {
self.add($input.val());
$input.val('');
event.preventDefault();
}
break;
}
$input.attr('size', Math.max(40, $input.val().length));
}, self));
// Remove icon clicked
self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
self.remove($(event.target).closest('.tag').data('item'));
}, self));
if (self.$element[0].tagName === 'INPUT') {
self.add(self.$element.val());
} else {
$('option', self.$element).each(function() {
self.add($(this).attr('value'), true);
});
}
},
destroy: function() {
var self = this;
// Unbind events
self.$container.off('keypress', 'input');
self.$container.off('click', '[50role=remove]');
self.$container.remove();
self.$element.removeData('tagsinput');
self.$element.show();
},
focus: function() {
this.$input.focus();
}
};
// Register JQuery plugin
$.fn.tagsinput = function(arg1, arg2) {
var results = [];
this.each(function() {
var tagsinput = $(this).data('tagsinput');
// Initialize a new tags input
if (!tagsinput) {
tagsinput = new TagsInput(this, arg1);
$(this).data('tagsinput', tagsinput);
results.push(tagsinput);
if (this.tagName === 'SELECT') {
$('option', $(this)).attr('selected', 'selected');
}
// Init tags from $(this).val()
$(this).val($(this).val());
} else {
// Invoke function on existing tags input
var retVal = tagsinput[arg1](arg2);
if (retVal !== undefined)
results.push(retVal);
}
});
if ( typeof arg1 == 'string') {
// Return the results from the invoked function calls
return results.length > 1 ? results : results[0];
} else {
return results;
}
};
$.fn.tagsinput.Constructor = TagsInput;
// Most options support both a string or number as well as a function as
// option value. This function makes sure that the option with the given
// key in the given options is wrapped in a function
function makeOptionItemFunction(options, key) {
if (typeof options[key] !== 'function') {
var value = options[key];
options[key] = function(item) { return item[value]; };
}
}
function makeOptionFunction(options, key) {
if (typeof options[key] !== 'function') {
var value = options[key];
options[key] = function() { return value; };
}
}
// HtmlEncodes the given value
var htmlEncodeContainer = $('<div />');
function htmlEncode(value) {
if (value) {
return htmlEncodeContainer.text(value).html();
} else {
return '';
}
}
// Returns the position of the caret in the given input field
// http://flightschool.acylt.com/devnotes/caret-position-woes/
function doGetCaretPosition(oField) {
var iCaretPos = 0;
if (document.selection) {
oField.focus ();
var oSel = document.selection.createRange();
oSel.moveStart ('character', -oField.value.length);
iCaretPos = oSel.text.length;
} else if (oField.selectionStart || oField.selectionStart == '0') {
iCaretPos = oField.selectionStart;
}
return (iCaretPos);
}
$(function() {
$("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
});
})(window.jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,44 @@
.bootstrap-tagsinput {
background-color: #fff;
border: 1px solid #ccc;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
display: inline-block;
padding: 4px 6px;
margin-bottom: 10px;
color: #555;
vertical-align: middle;
border-radius: 4px;
max-width: 100%;
line-height: 22px;
}
.bootstrap-tagsinput input {
border: none;
box-shadow: none;
background-color: transparent;
padding: 0;
margin: 0;
width: auto !important;
max-width: inherit;
}
.bootstrap-tagsinput input:focus {
border: none;
box-shadow: none;
}
.bootstrap-tagsinput .tag {
margin-right: 5px;
color: white;
}
.bootstrap-tagsinput .tag [data-role="remove"] {
margin-left: 8px;
cursor: pointer;
}
.bootstrap-tagsinput .tag [data-role="remove"]:after {
content: "x";
padding: 0px 2px;
}
.bootstrap-tagsinput .tag [data-role="remove"]:hover {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
}
.bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}

View File

@ -0,0 +1,48 @@
.bootstrap-tagsinput {
background-color: #fff;
border: 1px solid #ccc;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
display: inline-block;
padding: 4px 6px;
margin-bottom: 10px;
color: #555;
vertical-align: middle;
border-radius: 4px;
max-width: 100%;
line-height: 22px;
input {
border: none;
box-shadow: none;
background-color: transparent;
padding: 0;
margin: 0;
width: auto !important;
max-width: inherit;
&:focus {
border: none;
box-shadow: none;
}
}
.tag {
margin-right: 5px;
color: white;
[data-role="remove"] {
margin-left:8px;
cursor:pointer;
&:after{
content: "x";
padding:0px 2px;
}
&:hover {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
&:active {
box-shadow: inset 0 3px 5px rgba(0,0,0,0.125);
}
}
}
}
}