styling, tagging
This commit is contained in:
parent
466141adad
commit
67ed4565d4
1
Gemfile
1
Gemfile
@ -21,6 +21,7 @@ gem 'unicorn'
|
||||
gem 'bootstrap-sass', '<3.1'
|
||||
gem 'devise'
|
||||
gem 'jbuilder'
|
||||
gem 'bootstrap-tagsinput-rails'
|
||||
|
||||
|
||||
group :development do
|
||||
|
@ -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
|
||||
|
@ -12,4 +12,5 @@
|
||||
//
|
||||
//= require jquery
|
||||
//= require jquery_ujs
|
||||
//= require bootstrap-tagsinput
|
||||
//= require_tree .
|
||||
|
@ -3,5 +3,7 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@import 'bootstrap';
|
||||
@import 'bootstrap-tagsinput.css';
|
||||
@import 'campaign';
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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%;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
80
vendor/assets/javascripts/bootstrap-tagsinput-angular.js
vendored
Executable file
80
vendor/assets/javascripts/bootstrap-tagsinput-angular.js
vendored
Executable 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);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
405
vendor/assets/javascripts/bootstrap-tagsinput.js
vendored
Executable file
405
vendor/assets/javascripts/bootstrap-tagsinput.js
vendored
Executable 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);
|
8
vendor/assets/javascripts/bootstrap-tagsinput.min.js
vendored
Executable file
8
vendor/assets/javascripts/bootstrap-tagsinput.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
1
vendor/assets/javascripts/bootstrap-tagsinput.min.js.map
vendored
Executable file
1
vendor/assets/javascripts/bootstrap-tagsinput.min.js.map
vendored
Executable file
File diff suppressed because one or more lines are too long
44
vendor/assets/stylesheets/bootstrap-tagsinput.css
vendored
Executable file
44
vendor/assets/stylesheets/bootstrap-tagsinput.css
vendored
Executable 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);
|
||||
}
|
48
vendor/assets/stylesheets/bootstrap-tagsinput.less
vendored
Executable file
48
vendor/assets/stylesheets/bootstrap-tagsinput.less
vendored
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user