Commit 437e945c67eaf2fa915ef5e92602beca2f65f68c

Authored by Felipe Henrique de Almeida Bormann
1 parent 9d0c2c87

tags input working but still have to fix that they get the value from value prop…

…erty instead of text,  and fixed pagination amount
amadeus/static/css/bootstrap-tagsinput.css 0 → 100755
... ... @@ -0,0 +1,55 @@
  1 +.bootstrap-tagsinput {
  2 + background-color: #fff;
  3 + border: 1px solid #ccc;
  4 + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
  5 + display: inline-block;
  6 + padding: 4px 6px;
  7 + color: #555;
  8 + vertical-align: middle;
  9 + border-radius: 4px;
  10 + max-width: 100%;
  11 + line-height: 22px;
  12 + cursor: text;
  13 +}
  14 +.bootstrap-tagsinput input {
  15 + border: none;
  16 + box-shadow: none;
  17 + outline: none;
  18 + background-color: transparent;
  19 + padding: 0 6px;
  20 + margin: 0;
  21 + width: auto;
  22 + max-width: inherit;
  23 +}
  24 +.bootstrap-tagsinput.form-control input::-moz-placeholder {
  25 + color: #777;
  26 + opacity: 1;
  27 +}
  28 +.bootstrap-tagsinput.form-control input:-ms-input-placeholder {
  29 + color: #777;
  30 +}
  31 +.bootstrap-tagsinput.form-control input::-webkit-input-placeholder {
  32 + color: #777;
  33 +}
  34 +.bootstrap-tagsinput input:focus {
  35 + border: none;
  36 + box-shadow: none;
  37 +}
  38 +.bootstrap-tagsinput .tag {
  39 + margin-right: 2px;
  40 + color: white;
  41 +}
  42 +.bootstrap-tagsinput .tag [data-role="remove"] {
  43 + margin-left: 8px;
  44 + cursor: pointer;
  45 +}
  46 +.bootstrap-tagsinput .tag [data-role="remove"]:after {
  47 + content: "x";
  48 + padding: 0px 2px;
  49 +}
  50 +.bootstrap-tagsinput .tag [data-role="remove"]:hover {
  51 + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  52 +}
  53 +.bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
  54 + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
  55 +}
... ...
amadeus/static/js/bootstrap-tagsinput.js 0 → 100755
... ... @@ -0,0 +1,646 @@
  1 +(function ($) {
  2 + "use strict";
  3 +
  4 + var defaultOptions = {
  5 + tagClass: function(item) {
  6 + return 'label label-info';
  7 + },
  8 + itemValue: function(item) {
  9 + return item ? item.toString() : item;
  10 + },
  11 + itemText: function(item) {
  12 + return this.itemValue(item);
  13 + },
  14 + itemTitle: function(item) {
  15 + return null;
  16 + },
  17 + freeInput: true,
  18 + addOnBlur: true,
  19 + maxTags: undefined,
  20 + maxChars: undefined,
  21 + confirmKeys: [13, 44],
  22 + delimiter: ',',
  23 + delimiterRegex: null,
  24 + cancelConfirmKeysOnEmpty: true,
  25 + onTagExists: function(item, $tag) {
  26 + $tag.hide().fadeIn();
  27 + },
  28 + trimValue: false,
  29 + allowDuplicates: false
  30 + };
  31 +
  32 + /**
  33 + * Constructor function
  34 + */
  35 + function TagsInput(element, options) {
  36 + this.itemsArray = [];
  37 +
  38 + this.$element = $(element);
  39 + this.$element.hide();
  40 +
  41 + this.isSelect = (element.tagName === 'SELECT');
  42 + this.multiple = (this.isSelect && element.hasAttribute('multiple'));
  43 + this.objectItems = options && options.itemValue;
  44 + this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
  45 + this.inputSize = Math.max(1, this.placeholderText.length);
  46 +
  47 + this.$container = $('<div class="bootstrap-tagsinput"></div>');
  48 + this.$input = $('<input type="text" placeholder="' + this.placeholderText + '"/>').appendTo(this.$container);
  49 +
  50 + this.$element.before(this.$container);
  51 +
  52 + this.build(options);
  53 + }
  54 +
  55 + TagsInput.prototype = {
  56 + constructor: TagsInput,
  57 +
  58 + /**
  59 + * Adds the given item as a new tag. Pass true to dontPushVal to prevent
  60 + * updating the elements val()
  61 + */
  62 + add: function(item, dontPushVal, options) {
  63 + var self = this;
  64 +
  65 + if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
  66 + return;
  67 +
  68 + // Ignore falsey values, except false
  69 + if (item !== false && !item)
  70 + return;
  71 +
  72 + // Trim value
  73 + if (typeof item === "string" && self.options.trimValue) {
  74 + item = $.trim(item);
  75 + }
  76 +
  77 + // Throw an error when trying to add an object while the itemValue option was not set
  78 + if (typeof item === "object" && !self.objectItems)
  79 + throw("Can't add objects when itemValue option is not set");
  80 +
  81 + // Ignore strings only containg whitespace
  82 + if (item.toString().match(/^\s*$/))
  83 + return;
  84 +
  85 + // If SELECT but not multiple, remove current tag
  86 + if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
  87 + self.remove(self.itemsArray[0]);
  88 +
  89 + if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
  90 + var delimiter = (self.options.delimiterRegex) ? self.options.delimiterRegex : self.options.delimiter;
  91 + var items = item.split(delimiter);
  92 + if (items.length > 1) {
  93 + for (var i = 0; i < items.length; i++) {
  94 + this.add(items[i], true);
  95 + }
  96 +
  97 + if (!dontPushVal)
  98 + self.pushVal();
  99 + return;
  100 + }
  101 + }
  102 +
  103 + var itemValue = self.options.itemValue(item),
  104 + itemText = self.options.itemText(item),
  105 + tagClass = self.options.tagClass(item),
  106 + itemTitle = self.options.itemTitle(item);
  107 +
  108 + // Ignore items allready added
  109 + var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
  110 + if (existing && !self.options.allowDuplicates) {
  111 + // Invoke onTagExists
  112 + if (self.options.onTagExists) {
  113 + var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
  114 + self.options.onTagExists(item, $existingTag);
  115 + }
  116 + return;
  117 + }
  118 +
  119 + // if length greater than limit
  120 + if (self.items().toString().length + item.length + 1 > self.options.maxInputLength)
  121 + return;
  122 +
  123 + // raise beforeItemAdd arg
  124 + var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options});
  125 + self.$element.trigger(beforeItemAddEvent);
  126 + if (beforeItemAddEvent.cancel)
  127 + return;
  128 +
  129 + // register item in internal array and map
  130 + self.itemsArray.push(item);
  131 +
  132 + // add a tag element
  133 +
  134 + var $tag = $('<span class="tag ' + htmlEncode(tagClass) + (itemTitle !== null ? ('" title="' + itemTitle) : '') + '">' + htmlEncode(itemText) + '<span data-role="remove"></span></span>');
  135 + $tag.data('item', item);
  136 + self.findInputWrapper().before($tag);
  137 + $tag.after(' ');
  138 +
  139 + // add <option /> if item represents a value not present in one of the <select />'s options
  140 + if (self.isSelect && !$('option[value="' + encodeURIComponent(itemValue) + '"]',self.$element)[0]) {
  141 + var $option = $('<option selected>' + htmlEncode(itemText) + '</option>');
  142 + $option.data('item', item);
  143 + $option.attr('value', itemValue);
  144 + self.$element.append($option);
  145 + }
  146 +
  147 + if (!dontPushVal)
  148 + self.pushVal();
  149 +
  150 + // Add class when reached maxTags
  151 + if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength)
  152 + self.$container.addClass('bootstrap-tagsinput-max');
  153 +
  154 + self.$element.trigger($.Event('itemAdded', { item: item, options: options }));
  155 + },
  156 +
  157 + /**
  158 + * Removes the given item. Pass true to dontPushVal to prevent updating the
  159 + * elements val()
  160 + */
  161 + remove: function(item, dontPushVal, options) {
  162 + var self = this;
  163 +
  164 + if (self.objectItems) {
  165 + if (typeof item === "object")
  166 + item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } );
  167 + else
  168 + item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } );
  169 +
  170 + item = item[item.length-1];
  171 + }
  172 +
  173 + if (item) {
  174 + var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false, options: options });
  175 + self.$element.trigger(beforeItemRemoveEvent);
  176 + if (beforeItemRemoveEvent.cancel)
  177 + return;
  178 +
  179 + $('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
  180 + $('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
  181 + if($.inArray(item, self.itemsArray) !== -1)
  182 + self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
  183 + }
  184 +
  185 + if (!dontPushVal)
  186 + self.pushVal();
  187 +
  188 + // Remove class when reached maxTags
  189 + if (self.options.maxTags > self.itemsArray.length)
  190 + self.$container.removeClass('bootstrap-tagsinput-max');
  191 +
  192 + self.$element.trigger($.Event('itemRemoved', { item: item, options: options }));
  193 + },
  194 +
  195 + /**
  196 + * Removes all items
  197 + */
  198 + removeAll: function() {
  199 + var self = this;
  200 +
  201 + $('.tag', self.$container).remove();
  202 + $('option', self.$element).remove();
  203 +
  204 + while(self.itemsArray.length > 0)
  205 + self.itemsArray.pop();
  206 +
  207 + self.pushVal();
  208 + },
  209 +
  210 + /**
  211 + * Refreshes the tags so they match the text/value of their corresponding
  212 + * item.
  213 + */
  214 + refresh: function() {
  215 + var self = this;
  216 + $('.tag', self.$container).each(function() {
  217 + var $tag = $(this),
  218 + item = $tag.data('item'),
  219 + itemValue = self.options.itemValue(item),
  220 + itemText = self.options.itemText(item),
  221 + tagClass = self.options.tagClass(item);
  222 +
  223 + // Update tag's class and inner text
  224 + $tag.attr('class', null);
  225 + $tag.addClass('tag ' + htmlEncode(tagClass));
  226 + $tag.contents().filter(function() {
  227 + return this.nodeType == 3;
  228 + })[0].nodeValue = htmlEncode(itemText);
  229 +
  230 + if (self.isSelect) {
  231 + var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
  232 + option.attr('value', itemValue);
  233 + }
  234 + });
  235 + },
  236 +
  237 + /**
  238 + * Returns the items added as tags
  239 + */
  240 + items: function() {
  241 + return this.itemsArray;
  242 + },
  243 +
  244 + /**
  245 + * Assembly value by retrieving the value of each item, and set it on the
  246 + * element.
  247 + */
  248 + pushVal: function() {
  249 + var self = this,
  250 + val = $.map(self.items(), function(item) {
  251 + return self.options.itemValue(item).toString();
  252 + });
  253 +
  254 + self.$element.val(val, true).trigger('change');
  255 + },
  256 +
  257 + /**
  258 + * Initializes the tags input behaviour on the element
  259 + */
  260 + build: function(options) {
  261 + var self = this;
  262 +
  263 + self.options = $.extend({}, defaultOptions, options);
  264 + // When itemValue is set, freeInput should always be false
  265 + if (self.objectItems)
  266 + self.options.freeInput = false;
  267 +
  268 + makeOptionItemFunction(self.options, 'itemValue');
  269 + makeOptionItemFunction(self.options, 'itemText');
  270 + makeOptionFunction(self.options, 'tagClass');
  271 +
  272 + // Typeahead Bootstrap version 2.3.2
  273 + if (self.options.typeahead) {
  274 + var typeahead = self.options.typeahead || {};
  275 +
  276 + makeOptionFunction(typeahead, 'source');
  277 +
  278 + self.$input.typeahead($.extend({}, typeahead, {
  279 + source: function (query, process) {
  280 + function processItems(items) {
  281 + var texts = [];
  282 +
  283 + for (var i = 0; i < items.length; i++) {
  284 + var text = self.options.itemText(items[i]);
  285 + map[text] = items[i];
  286 + texts.push(text);
  287 + }
  288 + process(texts);
  289 + }
  290 +
  291 + this.map = {};
  292 + var map = this.map,
  293 + data = typeahead.source(query);
  294 +
  295 + if ($.isFunction(data.success)) {
  296 + // support for Angular callbacks
  297 + data.success(processItems);
  298 + } else if ($.isFunction(data.then)) {
  299 + // support for Angular promises
  300 + data.then(processItems);
  301 + } else {
  302 + // support for functions and jquery promises
  303 + $.when(data)
  304 + .then(processItems);
  305 + }
  306 + },
  307 + updater: function (text) {
  308 + self.add(this.map[text]);
  309 + return this.map[text];
  310 + },
  311 + matcher: function (text) {
  312 + return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
  313 + },
  314 + sorter: function (texts) {
  315 + return texts.sort();
  316 + },
  317 + highlighter: function (text) {
  318 + var regex = new RegExp( '(' + this.query + ')', 'gi' );
  319 + return text.replace( regex, "<strong>$1</strong>" );
  320 + }
  321 + }));
  322 + }
  323 +
  324 + // typeahead.js
  325 + if (self.options.typeaheadjs) {
  326 + var typeaheadConfig = null;
  327 + var typeaheadDatasets = {};
  328 +
  329 + // Determine if main configurations were passed or simply a dataset
  330 + var typeaheadjs = self.options.typeaheadjs;
  331 + if ($.isArray(typeaheadjs)) {
  332 + typeaheadConfig = typeaheadjs[0];
  333 + typeaheadDatasets = typeaheadjs[1];
  334 + } else {
  335 + typeaheadDatasets = typeaheadjs;
  336 + }
  337 +
  338 + self.$input.typeahead(typeaheadConfig, typeaheadDatasets).on('typeahead:selected', $.proxy(function (obj, datum) {
  339 + if (typeaheadDatasets.valueKey)
  340 + self.add(datum[typeaheadDatasets.valueKey]);
  341 + else
  342 + self.add(datum);
  343 + self.$input.typeahead('val', '');
  344 + }, self));
  345 + }
  346 +
  347 + self.$container.on('click', $.proxy(function(event) {
  348 + if (! self.$element.attr('disabled')) {
  349 + self.$input.removeAttr('disabled');
  350 + }
  351 + self.$input.focus();
  352 + }, self));
  353 +
  354 + if (self.options.addOnBlur && self.options.freeInput) {
  355 + self.$input.on('focusout', $.proxy(function(event) {
  356 + // HACK: only process on focusout when no typeahead opened, to
  357 + // avoid adding the typeahead text as tag
  358 + if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) {
  359 + self.add(self.$input.val());
  360 + self.$input.val('');
  361 + }
  362 + }, self));
  363 + }
  364 +
  365 +
  366 + self.$container.on('keydown', 'input', $.proxy(function(event) {
  367 + var $input = $(event.target),
  368 + $inputWrapper = self.findInputWrapper();
  369 +
  370 + if (self.$element.attr('disabled')) {
  371 + self.$input.attr('disabled', 'disabled');
  372 + return;
  373 + }
  374 +
  375 + switch (event.which) {
  376 + // BACKSPACE
  377 + case 8:
  378 + if (doGetCaretPosition($input[0]) === 0) {
  379 + var prev = $inputWrapper.prev();
  380 + if (prev.length) {
  381 + self.remove(prev.data('item'));
  382 + }
  383 + }
  384 + break;
  385 +
  386 + // DELETE
  387 + case 46:
  388 + if (doGetCaretPosition($input[0]) === 0) {
  389 + var next = $inputWrapper.next();
  390 + if (next.length) {
  391 + self.remove(next.data('item'));
  392 + }
  393 + }
  394 + break;
  395 +
  396 + // LEFT ARROW
  397 + case 37:
  398 + // Try to move the input before the previous tag
  399 + var $prevTag = $inputWrapper.prev();
  400 + if ($input.val().length === 0 && $prevTag[0]) {
  401 + $prevTag.before($inputWrapper);
  402 + $input.focus();
  403 + }
  404 + break;
  405 + // RIGHT ARROW
  406 + case 39:
  407 + // Try to move the input after the next tag
  408 + var $nextTag = $inputWrapper.next();
  409 + if ($input.val().length === 0 && $nextTag[0]) {
  410 + $nextTag.after($inputWrapper);
  411 + $input.focus();
  412 + }
  413 + break;
  414 + default:
  415 + // ignore
  416 + }
  417 +
  418 + // Reset internal input's size
  419 + var textLength = $input.val().length,
  420 + wordSpace = Math.ceil(textLength / 5),
  421 + size = textLength + wordSpace + 1;
  422 + $input.attr('size', Math.max(this.inputSize, $input.val().length));
  423 + }, self));
  424 +
  425 + self.$container.on('keypress', 'input', $.proxy(function(event) {
  426 + var $input = $(event.target);
  427 +
  428 + if (self.$element.attr('disabled')) {
  429 + self.$input.attr('disabled', 'disabled');
  430 + return;
  431 + }
  432 +
  433 + var text = $input.val(),
  434 + maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
  435 + if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
  436 + // Only attempt to add a tag if there is data in the field
  437 + if (text.length !== 0) {
  438 + self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
  439 + $input.val('');
  440 + }
  441 +
  442 + // If the field is empty, let the event triggered fire as usual
  443 + if (self.options.cancelConfirmKeysOnEmpty === false) {
  444 + event.preventDefault();
  445 + }
  446 + }
  447 +
  448 + // Reset internal input's size
  449 + var textLength = $input.val().length,
  450 + wordSpace = Math.ceil(textLength / 5),
  451 + size = textLength + wordSpace + 1;
  452 + $input.attr('size', Math.max(this.inputSize, $input.val().length));
  453 + }, self));
  454 +
  455 + // Remove icon clicked
  456 + self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
  457 + if (self.$element.attr('disabled')) {
  458 + return;
  459 + }
  460 + self.remove($(event.target).closest('.tag').data('item'));
  461 + }, self));
  462 +
  463 + // Only add existing value as tags when using strings as tags
  464 + if (self.options.itemValue === defaultOptions.itemValue) {
  465 + if (self.$element[0].tagName === 'INPUT') {
  466 + self.add(self.$element.val());
  467 + } else {
  468 + $('option', self.$element).each(function() {
  469 + self.add($(this).attr('value'), true);
  470 + });
  471 + }
  472 + }
  473 + },
  474 +
  475 + /**
  476 + * Removes all tagsinput behaviour and unregsiter all event handlers
  477 + */
  478 + destroy: function() {
  479 + var self = this;
  480 +
  481 + // Unbind events
  482 + self.$container.off('keypress', 'input');
  483 + self.$container.off('click', '[role=remove]');
  484 +
  485 + self.$container.remove();
  486 + self.$element.removeData('tagsinput');
  487 + self.$element.show();
  488 + },
  489 +
  490 + /**
  491 + * Sets focus on the tagsinput
  492 + */
  493 + focus: function() {
  494 + this.$input.focus();
  495 + },
  496 +
  497 + /**
  498 + * Returns the internal input element
  499 + */
  500 + input: function() {
  501 + return this.$input;
  502 + },
  503 +
  504 + /**
  505 + * Returns the element which is wrapped around the internal input. This
  506 + * is normally the $container, but typeahead.js moves the $input element.
  507 + */
  508 + findInputWrapper: function() {
  509 + var elt = this.$input[0],
  510 + container = this.$container[0];
  511 + while(elt && elt.parentNode !== container)
  512 + elt = elt.parentNode;
  513 +
  514 + return $(elt);
  515 + }
  516 + };
  517 +
  518 + /**
  519 + * Register JQuery plugin
  520 + */
  521 + $.fn.tagsinput = function(arg1, arg2, arg3) {
  522 + var results = [];
  523 +
  524 + this.each(function() {
  525 + var tagsinput = $(this).data('tagsinput');
  526 + // Initialize a new tags input
  527 + if (!tagsinput) {
  528 + tagsinput = new TagsInput(this, arg1);
  529 + $(this).data('tagsinput', tagsinput);
  530 + results.push(tagsinput);
  531 +
  532 + if (this.tagName === 'SELECT') {
  533 + $('option', $(this)).attr('selected', 'selected');
  534 + }
  535 +
  536 + // Init tags from $(this).val()
  537 + $(this).val($(this).val());
  538 + } else if (!arg1 && !arg2) {
  539 + // tagsinput already exists
  540 + // no function, trying to init
  541 + results.push(tagsinput);
  542 + } else if(tagsinput[arg1] !== undefined) {
  543 + // Invoke function on existing tags input
  544 + if(tagsinput[arg1].length === 3 && arg3 !== undefined){
  545 + var retVal = tagsinput[arg1](arg2, null, arg3);
  546 + }else{
  547 + var retVal = tagsinput[arg1](arg2);
  548 + }
  549 + if (retVal !== undefined)
  550 + results.push(retVal);
  551 + }
  552 + });
  553 +
  554 + if ( typeof arg1 == 'string') {
  555 + // Return the results from the invoked function calls
  556 + return results.length > 1 ? results : results[0];
  557 + } else {
  558 + return results;
  559 + }
  560 + };
  561 +
  562 + $.fn.tagsinput.Constructor = TagsInput;
  563 +
  564 + /**
  565 + * Most options support both a string or number as well as a function as
  566 + * option value. This function makes sure that the option with the given
  567 + * key in the given options is wrapped in a function
  568 + */
  569 + function makeOptionItemFunction(options, key) {
  570 + if (typeof options[key] !== 'function') {
  571 + var propertyName = options[key];
  572 + options[key] = function(item) { return item[propertyName]; };
  573 + }
  574 + }
  575 + function makeOptionFunction(options, key) {
  576 + if (typeof options[key] !== 'function') {
  577 + var value = options[key];
  578 + options[key] = function() { return value; };
  579 + }
  580 + }
  581 + /**
  582 + * HtmlEncodes the given value
  583 + */
  584 + var htmlEncodeContainer = $('<div />');
  585 + function htmlEncode(value) {
  586 + if (value) {
  587 + return htmlEncodeContainer.text(value).html();
  588 + } else {
  589 + return '';
  590 + }
  591 + }
  592 +
  593 + /**
  594 + * Returns the position of the caret in the given input field
  595 + * http://flightschool.acylt.com/devnotes/caret-position-woes/
  596 + */
  597 + function doGetCaretPosition(oField) {
  598 + var iCaretPos = 0;
  599 + if (document.selection) {
  600 + oField.focus ();
  601 + var oSel = document.selection.createRange();
  602 + oSel.moveStart ('character', -oField.value.length);
  603 + iCaretPos = oSel.text.length;
  604 + } else if (oField.selectionStart || oField.selectionStart == '0') {
  605 + iCaretPos = oField.selectionStart;
  606 + }
  607 + return (iCaretPos);
  608 + }
  609 +
  610 + /**
  611 + * Returns boolean indicates whether user has pressed an expected key combination.
  612 + * @param object keyPressEvent: JavaScript event object, refer
  613 + * http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
  614 + * @param object lookupList: expected key combinations, as in:
  615 + * [13, {which: 188, shiftKey: true}]
  616 + */
  617 + function keyCombinationInList(keyPressEvent, lookupList) {
  618 + var found = false;
  619 + $.each(lookupList, function (index, keyCombination) {
  620 + if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
  621 + found = true;
  622 + return false;
  623 + }
  624 +
  625 + if (keyPressEvent.which === keyCombination.which) {
  626 + var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
  627 + shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
  628 + ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
  629 + if (alt && shift && ctrl) {
  630 + found = true;
  631 + return false;
  632 + }
  633 + }
  634 + });
  635 +
  636 + return found;
  637 + }
  638 +
  639 + /**
  640 + * Initialize tagsinput behaviour on inputs and selects which have
  641 + * data-role=tagsinput
  642 + */
  643 + $(function() {
  644 + $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
  645 + });
  646 +})(window.jQuery);
... ...
subjects/templates/subjects/create.html
... ... @@ -2,6 +2,20 @@
2 2  
3 3 {% load widget_tweaks static i18n permission_tags django_bootstrap_breadcrumbs switchevenodd %}
4 4  
  5 +{% block style %}
  6 +
  7 + {{block.super}}
  8 + <link rel="stylesheet" type="text/css" href="{% static "css/bootstrap-tagsinput.css" %}">
  9 +
  10 +
  11 +{% endblock style %}
  12 +
  13 +{% block javascript %}
  14 + {{block.super}}
  15 + <script type="text/javascript" src="{% static "js/bootstrap-tagsinput.js" %} "></script>
  16 +
  17 +{% endblock javascript %}
  18 +
5 19 {% block breadcrumbs %}
6 20 {% clear_breadcrumbs %}
7 21 {% breadcrumb 'Home' 'subjects:home' %}
... ... @@ -57,6 +71,9 @@
57 71 </div>
58 72 </div>
59 73 </div>
  74 + {% elif field.auto_id == 'id_markers'%}
  75 + <label> {{field.label}} </label>
  76 + {% render_field field class='form-control' data-role="tagsinput" %}
60 77 {% else %}
61 78 <div class="form-group {% if form.has_error %} has-error {% endif %} is-fileinput">
62 79 {% if field.auto_id != 'id_visible' %}
... ... @@ -190,6 +207,8 @@
190 207 btn.switchClass("fa-angle-down", "fa-angle-right", 250, "easeInOutQuad");
191 208 }
192 209 });
  210 +
  211 +
193 212 </script>
194 213 {% endblock content %}
195 214  
... ...
subjects/views.py
... ... @@ -42,7 +42,7 @@ class HomeView(LoginRequiredMixin, ListView):
42 42 subjects = Subject.objects.all()
43 43 subjects = [subject for subject in subjects if self.request.user in subject.students.all() or self.request.user in subject.professor.all()]
44 44  
45   - paginator = Paginator(subjects, 2)
  45 + paginator = Paginator(subjects, 10)
46 46  
47 47 page = self.request.GET.get('page')
48 48 try:
... ...