]> 91.132.146.200 Git - insipid.git/commitdiff
datalist with suggestion and modified version to allow entries which are not in the...
authorBanana <banana@optimus.de>
Sun, 25 Dec 2016 18:53:21 +0000 (19:53 +0100)
committerBanana <banana@optimus.de>
Sun, 25 Dec 2016 18:53:21 +0000 (19:53 +0100)
source/flexdatalist/jquery-flexdatalist-1.8.1.zip [new file with mode: 0644]
source/flexdatalist/jquery.flexdatalist.inspid.js [new file with mode: 0644]
webroot/asset/flexdatalist/jquery.flexdatalist.min.css [new file with mode: 0644]
webroot/asset/flexdatalist/jquery.flexdatalist.min.js [new file with mode: 0644]
webroot/asset/img/insipid.png [new file with mode: 0644]
webroot/lib/management.class.php
webroot/view/_foot.php
webroot/view/_head.php
webroot/view/home.inc.php
webroot/view/home.php

diff --git a/source/flexdatalist/jquery-flexdatalist-1.8.1.zip b/source/flexdatalist/jquery-flexdatalist-1.8.1.zip
new file mode 100644 (file)
index 0000000..7ca3757
Binary files /dev/null and b/source/flexdatalist/jquery-flexdatalist-1.8.1.zip differ
diff --git a/source/flexdatalist/jquery.flexdatalist.inspid.js b/source/flexdatalist/jquery.flexdatalist.inspid.js
new file mode 100644 (file)
index 0000000..ce224a2
--- /dev/null
@@ -0,0 +1,1200 @@
+/**
+ * jQuery Flexdatalist.
+ * Autocomplete for input fields with support for datalists.
+ *
+ * Version:
+ * 1.8.0
+ *
+ * Depends:
+ * jquery.js 1.7+
+ *
+ * Demo and Documentation:
+ * http://projects.sergiodinislopes.pt/flexdatalist/
+ *
+ * Github:
+ * https://github.com/sergiodlopes/jquery-flexdatalist/
+ *
+ */
+
+jQuery.fn.flexdatalist = function (options, value) {
+    'use strict';
+    var $document = $(document),
+        $input = $(this),
+        input,
+        _this = this;
+
+/**
+ * Each iteration.
+ */
+    input = function () {
+        var $this = $(this),
+            _cache = {},
+            _previousText = '',
+            _requestTimeout = null,
+            _inputName = $this.attr('name');
+
+        if ($this.hasClass('flexdatalist-set')) {
+            _this._destroy($this);
+        }
+
+    /**
+     * Option management.
+     */
+        $this._options = function (option, _value) {
+            var _options = $this.data('flexdatalist');
+            if (!_this._isDefined(option)) {
+                return $this.data('flexdatalist');
+            } else if (_this._isDefined(_value)) {
+                _options[option] = _value;
+            } else if (!_this._isObject(option)) {
+                return (_this._isDefined(_options, option) ? _options[option] : null);
+            } else {
+                _options = option;
+            }
+
+            _options.searchIn = _this._csvToArray(_options.searchIn);
+            _options.relatives = _options.relatives && $(_options.relatives).length > 0 ? $(_options.relatives) : null;
+            _options.textProperty = _options.textProperty === null ? _options.searchIn[0] : _options.textProperty;
+            _options.visibleProperties = _this._csvToArray(_options.visibleProperties, _options.searchIn);
+            $this.data('flexdatalist', _options);
+            return $this;
+        }
+
+        $this._options($.extend({
+                url: null,
+                data: [],
+                params: {},
+                relatives: null,
+                chainedRelatives: false,
+                cache: true,
+                minLength: 2,
+                groupBy: false,
+                selectionRequired: false,
+                focusFirstResult: false,
+                textProperty: null,
+                valueProperty: null,
+                visibleProperties: [],
+                searchIn: ['label'],
+                searchContain: false,
+                searchEqual: false,
+                searchDisabled: false, // New
+                normalizeString: null,
+                multiple: $this.attr('multiple'),
+                maxShownResults: 100,
+                noResultsText: 'No results found for "{keyword}"',
+                toggleSelected: false, // New
+                _values: []
+            }, options, $this.data())
+        );
+
+        // Handle multiple values
+        var $_this = $this
+                .clone(false)
+                .attr({
+                    'list': null,
+                    'name': null,
+                    'id': ($this.attr('id') ? $this.attr('id') + '-flexdatalist' : null)
+                })
+                .addClass('flexdatalist-alias')
+                .removeClass('flexdatalist');
+        if ($this._options('multiple')) {
+            var $ulMultiple = $('<ul>')
+                .addClass('flexdatalist-multiple')
+                .css({
+                    'background-color': $this.css('background-color'),
+                    'border-color': $this.css('border-left-color'),
+                    'border-width': $this.css('border-left-width'),
+                    'border-style': $this.css('border-left-style'),
+                    'border-radius': $this.css('border-top-left-radius')
+                })
+                .insertAfter($this).click(function () {
+                    $(this).find('input').focus();
+                });
+            var $li = $('<li class="input-container">')
+                .addClass('flexdatalist-multiple-value')
+                .append($_this)
+                .appendTo($ulMultiple);
+        } else {
+            $_this.insertAfter($this);
+        }
+        $this.addClass('flexdatalist').attr('type', 'hidden');
+
+    /**
+     * Initialize.
+     */
+        $this._init = function () {
+            var _options = $this._options();
+            // Listen to parent input key presses and state events.
+            $_this.on('input keydown', function (event) {
+                var val = $this._keyword();
+                // Comma separated values
+                if (_this._keyNum(event) === 188 && !_options.selectionRequired && _options.multiple) {
+                    event.preventDefault();
+                    $this._value(val);
+                    $this._removeResults();
+                // Remove results on tab away
+                } else if (_this._keyNum(event) === 9) {
+                    $this._removeResults();
+                // Check if is to remove previous value on backspace key
+                } else if (val.length === 0 && _options.multiple && _this._keyNum(event) === 8) {
+                    $_this.data('_remove', $_this.parents('li:eq(0)').prev());
+                }
+            }).on('input keyup', function (event) {
+                if ($this._changed() && _this._keyNum(event) !== 13) {
+                    var keyword = $this._keyword();
+                    if (keyword.length >= _options.minLength) {
+                        $this._search(function (matches) {
+                            $this._showResults(matches);
+                        });
+                    } else {
+                        $this._removeResults();
+                    }
+                    if (!_options.multiple) {
+                        if (!_options.selectionRequired) {
+                            $this._value(keyword);
+                        } else {
+                            $this._value('');
+                        }
+                    }
+                }
+                // Remove previous value on backspace key
+                var $remove = $_this.data('_remove');
+                if ($remove) {
+                    $remove.find('.fdl-remove').click();
+                    $_this.data('_remove', null);
+                }
+                _previousText = $this._keyword();
+            }).focus(function () {
+                var val = $this._keyword();
+                if (_options.minLength === 0) {
+                    if (val === '') {
+                        $this._tdata(function (data) {
+                            $this._showResults(data);
+                        });
+                    }
+                // Redo search on focus if not selected yet
+                } else if (val.length >= _options.minLength && !$this._selected()) {
+                    $this._search(function (matches) {
+                        $this._showResults(matches);
+                    });
+                }
+            })
+            .attr('autocomplete', 'off');
+
+            // Respect autofocus attribute
+            if ($_this.attr('autofocus')) {
+                $_this.focus();
+            }
+            window.onresize = function (event) {
+                $this._position();
+            };
+            $this.addClass('flexdatalist-set');
+        }
+
+    /**
+     * Check if field's text has changed.
+     */
+        $this._changed = function () {
+            return _previousText !== $this._keyword();
+        }
+
+    /**
+     * Check chained relatives.
+     */
+        $this._chained = function () {
+            var _options = $this._options();
+            if (!_options.relatives || !_options.chainedRelatives) {
+                return;
+            }
+            var toggle = function (init) {
+                _options.relatives.each(function () {
+                    var disabled = _this._isEmpty($(this).val()),
+                        empty = _this._isEmpty($this.val());
+                    $_this.prop('disabled', disabled);
+                    if (!init && (disabled || !empty)) {
+                        $this._value('');
+                        $_this.val('');
+                        if (_options.multiple) {
+                            $ulMultiple.find('li .remove').click();
+                        }
+                    }
+                });
+            }
+            _options.relatives.on('change', function () {
+                toggle();
+                _cache = {};
+            });
+            toggle(true);
+        }
+
+    /**
+     * Process value in input on init.
+     */
+        $this._initValue = function () {
+            var value = $this.attr('value');
+            if (_this._isEmpty(value)) {
+                return;
+            }
+            $this._options('originalValue', $this.val());
+            $this._parseValue(value, function (values) {
+                $this.val('', true);
+                $_this.val('');
+                if (!_this._isEmpty(values)) {
+                    $this._values(values);
+                }
+                _previousText = $this._keyword();
+            });
+        }
+
+    /**
+     * Parse initial value.
+     */
+        $this._parseValue = function (data, callback) {
+            var _options = $this._options();
+            if ($this._toJSON()) {
+                try {
+                    callback(JSON.parse(data));
+                } catch (e) {}
+            } else if ($this._toCSV() || typeof _options.valueProperty === 'string') {
+                var values = data.split(',');
+                if (typeof _options.valueProperty === 'string') {
+                    var _searchIn = _options.searchIn;
+                    _options.searchIn = _options.valueProperty.split(',');
+                    _options.searchEqual = true;
+                    $this._search(function (matches) {
+                        if (matches.length > 0) {
+                            callback(matches);
+                        }
+                        _options.searchIn = _searchIn;
+                        _options.searchEqual = false;
+                    }, values);
+                } else {
+                    callback(values);
+                }
+            } else {
+                callback(data);
+            }
+        }
+
+    /**
+     * Get data.
+     */
+        $this._tdata = function (callback) {
+            $this.trigger('before:flexdatalist.data');
+            $this._url(function (remoteData) {
+                $this._data(function (data) {
+                    data = data.concat(remoteData);
+                    // Check for already set values
+                    var values = $this._options('_values');
+                    for (var i = 0; i < data.length; i++) {
+                        var item = data[i];
+                        if (values && values.indexOf($this._getText(item)) > -1) {
+                            delete data[i];
+                        }
+                    }                    
+                    $this.trigger('after:flexdatalist.data', [data]);
+                    callback(data);
+                });
+            });
+        }
+
+    /**
+     * Get static data.
+     */
+        $this._data = function (callback) {
+            var _options = $this._options();
+            if (typeof _options.data === 'string') {
+                $this._remote({
+                    url: _options.data,
+                    success: function (data) {
+                        var _data = $this._getRemoteData(data);
+                        _options.data = _data;
+                        callback(_data);
+                    }
+                });
+            } else {
+                callback(_options.data);
+            }
+        }
+
+    /**
+     * Get remote data.
+     */
+        $this._url = function (callback) {
+            var _options = $this._options(),
+                keyword = $this._keyword(),
+                value = $this.val(),
+                cacheKey = keyword;
+
+            if (_this._isEmpty(_options.url)) {
+                return callback([]);
+            }
+
+            clearTimeout(_requestTimeout);
+            _requestTimeout = setTimeout(function () {
+                if (_options.cache && _options.cache !== 2) {
+                    cacheKey = keyword.substring(0, (_options.minLength > 0 ? _options.minLength : 1));
+                }
+
+                // Check cache
+                var cachedData = $this._cache(cacheKey);
+                if (cachedData) {
+                    callback(cachedData);
+                    return;
+                }
+
+                $this._remote({
+                    url: _options.url,
+                    data: $.extend($this._relativesData(), _options.params, {
+                            keyword: keyword,
+                            contain: _options.searchContain,
+                            selected: value
+                        }
+                    ),
+                    success: function (data) {
+                        var _data = $this._getRemoteData(data),
+                            _keyword = $this._keyword();
+                        if (_keyword.length > keyword.length) {
+                            $this._search(function (matches) {
+                                $this._showResults(matches);
+                            });                            
+                        } else {
+                            callback(_data);
+                        }
+                        $this._cache(cacheKey, _data);
+                    }
+                });
+            }, 200);
+        }
+
+    /**
+     * AJAX request.
+     */
+        $this._remote = function (options) {
+            // Prevent get data when pressing back button
+            if ($this.hasClass('flexdatalist-loading')) {
+                return;
+            }
+            $this.addClass('flexdatalist-loading');
+            options = $.extend({
+                type: 'post',
+                dataType: 'json',
+                complete: function () {
+                    $this.removeClass('flexdatalist-loading');
+                }
+            }, options);
+            $.ajax(options);
+        }
+
+    /**
+     * Extract remote data from server response.
+     */
+        $this._getRemoteData = function (data) {
+            var _data = data.results ? data.results : data;
+            if (typeof _data === 'string' && _data.indexOf('[{') === 0) {
+                _data = JSON.parse(_data);
+            }
+            if (_this._isObject(_data)) {
+                return _data;
+            }
+            return [];
+        }
+
+    /**
+     * Get relatives data.
+     */
+        $this._relativesData = function () {
+            var relatives = $this._options('relatives'),
+                data = {};
+            if (relatives) {
+                data['relatives'] = {};
+                relatives.each(function () {
+                    var $input = $(this);
+                    data['relatives'][$input.attr('name')] = $input.val();
+                });
+            }
+            return data;
+        }
+
+    /**
+     * Set datalist data, if exists.
+     */
+        $this._datalist = function () {
+            var _options = $this._options(),
+                list = $this.attr('list');
+            if (!_this._isEmpty(list)) {
+                _options.data = [];
+                $('#' + list).find('option').each(function () {
+                    var val = $(this).val();
+                    _options.data.push({
+                        label: val,
+                        value: val
+                    });
+                });
+            }
+            return $this;
+        }
+
+    /**
+     * Cached data.
+     */
+        $this._cache = function (key, data) {
+            if ($this._options('cache')) {
+                key = $this._normalizeString(key);
+                if (!_this._isDefined(data)) {
+                    if (_this._isDefined(_cache, key)) {
+                        data = _cache[key];
+                    }
+                    return data;
+                }
+                _cache[key] = data;
+            }
+            return null;
+        }
+
+    /**
+     * Search for keywords in data and return matches.
+     */
+        $this._search = function (callback, keywords) {
+            $this._tdata(function (data) {
+                var matches = [],
+                    _options = $this._options();
+                // If search disabled, return
+                if (_options.searchDisabled) {
+                    return callback(data);
+                }
+                if (!_this._isDefined(keywords)) {
+                    keywords = $this._keyword();
+                }
+                if (typeof keywords === 'string') {
+                    keywords = [keywords];
+                }
+                $this.trigger('before:flexdatalist.search', [keywords, data]);
+                for (var kwindex = 0; kwindex < keywords.length; kwindex++) {
+                    var keyword = keywords[kwindex];
+                    for (var index = 0; index < data.length; index++) {
+                        var _data = $this._matches(data[index], keyword, _options.values);
+                        if (!_data) {
+                            continue;
+                        }
+                        matches.push(_data);
+                    }
+                }
+                $this.trigger('after:flexdatalist.search', [keywords, data, matches]);
+                callback(matches);
+            });
+        }
+
+    /**
+     * Match against searchable properties.
+     */
+        $this._matches = function (item, keyword, values) {
+            var hasMatches = false,
+                _item = $.extend({}, item),
+                _options = $this._options(),
+                searchIn = _options.searchIn;
+
+            for (var index = 0; index < searchIn.length; index++) {
+                var searchProperty = searchIn[index];
+                if (!_this._isDefined(item, searchProperty) || !item[searchProperty]) {
+                    continue;
+                }
+                var text = item[searchProperty].toString();
+                if ($this._find(keyword, text)) {
+                    _item[searchProperty + '_highlight'] = $this._highlight(keyword, text);
+                    hasMatches = true;
+                }
+            }
+            return hasMatches ? _item : null;
+        }
+
+    /**
+     * Wrap found keyword with span.highlight.
+     */
+        $this._highlight = function (keyword, text) {
+            return text.replace(
+                new RegExp(keyword, ($this._options('searchContain') ? "ig" : "i")),
+                '<span class="highlight">$&</span>'
+            );
+        }
+
+    /**
+     * Search for keyword in string.
+     */
+        $this._find = function (keyword, text) {
+            var _options = $this._options();
+            text = $this._normalizeString(text),
+            keyword = $this._normalizeString(keyword);
+            if (_options.searchEqual) {
+                return text == keyword;
+            }
+            return (_options.searchContain ? (text.indexOf(keyword) >= 0) : (text.indexOf(keyword) === 0));
+        }
+
+    /**
+     * Show results.
+     */
+        $this._showResults = function (data) {
+            $this._removeResults(true);
+            var _options = $this._options();
+            
+            if (data.length === 0) {
+                //$this._noResults(_options.noResultsText);
+                //return;
+                
+                // extended for insipid usage.
+                // allow the not found one as new ones.
+                var kw = $this._keyword();
+                data = [{
+                       label: kw,
+                       value: kw,
+                       label_highlight: '<span class="highlight">'+kw+'</span>'
+                }];
+            }
+
+            var $ul = $this._getResultsContainer();
+            if (!_options.groupBy) {
+               console.log(data);
+                $this._items(data, $ul);
+            } else {
+                data = $this._groupData(data);
+                Object.keys(data).forEach(function (groupName, index) {
+                    var items = data[groupName],
+                        property = _options.groupBy,
+                        groupText = $this._getHighlight(items[0], property, groupName);
+
+                    var $li = $('<li>')
+                            .addClass('group')
+                            .append($('<span>')
+                                .addClass('group-name')
+                                .html(groupText)
+                            )
+                            .append($('<span>')
+                                .addClass('group-item-count')
+                                .text(' ' + items.length)
+                            )
+                            .appendTo($ul);
+
+                    $this._items(items, $ul);
+                });
+            }
+
+            var $li = $ul.find('li:not(.group)');
+            $li.on('click', function (event) {
+                var item = $(this).data('item');
+                if (item) {
+                    $this._selected(true)._removeResults()._value(item);
+                    $this.trigger('select:flexdatalist', [item, _options]);
+                }
+            }).hover(function () {
+                $li.removeClass('active');
+                $(this).addClass('active');
+            }, function () {
+                $(this).removeClass('active');
+            });
+
+            if (_options.focusFirstResult) {
+                $li.filter(':first').addClass('active');
+            }
+        }
+
+    /**
+     * No results found text.
+     */
+        $this._noResults = function (text) {
+            if (_this._isEmpty(text)) {
+                return;
+            }
+            var $container = $this._getResultsContainer(),
+                keyword = $this._keyword();
+                
+            text = text.split('{keyword}').join(keyword);
+            $('<li>')
+                .addClass('item no-results')
+                .append(text)
+                .appendTo($container)
+        }
+
+    /**
+     * Group data by property name.
+     */
+        $this._groupData = function (items) {
+            var data = [],
+                groupProperty = $this._options('groupBy');
+            for (var index = 0; index < items.length; index++) {
+                var _data = items[index];
+                if (_this._isDefined(_data, groupProperty)) {
+                    var propertyValue = _data[groupProperty];
+                    if (!_this._isDefined(data, propertyValue)) {
+                        data[propertyValue] = [];
+                    }
+                    data[propertyValue].push(_data);
+                }
+            }
+            return data;
+        }
+
+    /**
+     * Items iteration.
+     */
+        $this._items = function (items, $resultsContainer) {
+            var max = $this._options('maxShownResults');
+            $this.trigger('show:flexdatalist.results', [items]);
+            for (var index = 0; index < items.length; index++) {
+                if (max > 0 && max === index) {
+                    break;
+                }
+                $this._item(items[index]).appendTo($resultsContainer);
+            }
+            $this.trigger('shown:flexdatalist.results', [items]);
+        }
+
+    /**
+     * Result item creation.
+     */
+        $this._item = function (item) {
+            var $li = $('<li>')
+                    .data('item', item)
+                    .addClass('item'),
+                _options = $this._options(),
+                visibleProperties = _options.visibleProperties;
+
+            for (var index = 0; index < visibleProperties.length; index++) {
+                var visibleProperty = visibleProperties[index];
+                if (_options.groupBy && _options.groupBy === visibleProperty || !_this._isDefined(item, visibleProperty)) {
+                    continue;
+                }
+                var $item = {};
+                if (visibleProperty === 'thumb') {
+                    // Thumbnail image
+                    $item = $('<img>')
+                        .addClass('item item-' + visibleProperty)
+                        .attr('src', item[visibleProperty]);
+                } else {
+                    var propertyText = $this._getHighlight(item, visibleProperty);
+                    // Other text properties
+                    $item = $('<span>')
+                        .addClass('item item-' + visibleProperty)
+                        .html(propertyText + ' ');
+                }
+                $item.appendTo($li);
+            }
+            return $li;
+        }
+
+    /**
+     * Check if highlighted property value exists,
+     * if true, return it, if not, fallback to given string
+     */
+        $this._getHighlight = function (item, property, fallback) {
+            if (_this._isDefined(item, property + '_highlight')) {
+                return item[property + '_highlight'];
+            }
+            return (_this._isDefined(item, property) ? item[property] : fallback);
+        }
+
+    /**
+     * Get/create list container.
+     */
+        $this._getResultsContainer = function () {
+            var $target = $this;
+            if ($this._options('multiple')) {
+                $target = $ulMultiple;
+            }
+            var $container = $('ul.flexdatalist-results');
+            if ($container.length === 0) {
+                $container = $('<ul>')
+                    .addClass('flexdatalist-results')
+                    .appendTo('body')
+                    .css({
+                        'border-color': $target.css("border-left-color"),
+                        'border-width': '1px',
+                        'border-bottom-left-radius': $target.css("border-bottom-left-radius"),
+                        'border-bottom-right-radius': $target.css("border-bottom-right-radius")
+                    }).data('target', $_this);
+                $this._position();
+            }
+            return $container;
+        }
+
+    /**
+     * Remove results.
+     */
+        $this._removeResults = function (itemsOnly) {
+            var selector = 'ul.flexdatalist-results';
+            if (itemsOnly) {
+                selector = 'ul.flexdatalist-results li';
+            }
+            $(selector).remove();
+            return $this;
+        }
+
+    /**
+     * Check if is selected or set field as selected.
+     */
+        $this._selected = function (selected) {
+            var className = 'flexdatalist-selected';
+            if (!_this._isDefined(selected)) {
+                return $this.hasClass(className);
+            }
+            selected ? $this.addClass(className) : $this.removeClass(className);
+            return $this;
+        }
+
+    /**
+     * Set multiple values.
+     */
+        $this._values = function (values) {
+            if ($.isArray(values) && !_this._isEmpty(values)) {
+                $.each(values, function (i, value) {
+                    $this._value(value);
+                });
+                return;
+            }
+            $this._value(values);
+        }
+
+    /**
+     * Set value on item selection.
+     */
+        $this._value = function (val) {
+            var _options = $this._options(),
+                text = $this._getText(val),
+                value = $this._getValue(val);
+
+            if (text.length > 0) {
+                _options._values.push(text);
+            }
+
+            if (_options.multiple) {
+                if (val === '') {
+                    return $this;
+                }
+                $_this.val('');
+                var $li = $('<li>')
+                        .addClass('value' + (_options.toggleSelected ? ' toggle' : ''))
+                        .append('<span class="text">' + text + '</span>')
+                        .append('<span class="fdl-remove">&times;</span>')
+                        .insertBefore($ulMultiple.find('li.input-container'));
+
+                $li.find('span.fdl-remove').click(function () {
+                    var $container = $(this).parent(),
+                        index = $container.index();
+                    if (!$container.hasClass('disabled') && ($this._toJSON() || $this._toCSV())) {
+                        var currentValue = $this._inputValue();
+                        currentValue.splice(index, 1);
+                        _options._values.splice(index, 1);
+                        $this._inputValue(currentValue);
+                    }
+                    $container.remove();
+                });
+                // Toggle selected option
+                if (_options.toggleSelected) {
+                    $li.click(function () {
+                        var $clicked = $(this),
+                            currentValue = $this._inputValue(),
+                            index = $clicked.index();
+                        if ($clicked.hasClass('disabled')) {
+                            var value = $clicked.data('_value');
+                            currentValue.splice(index, 0, value);
+                            _options._values.splice(index, 0, $this._getText(value));
+                            $clicked.removeClass('disabled');
+                        } else {
+                            var value = currentValue.splice(index, 1);
+                            $clicked.data('_value', value[0]);
+                            _options._values.splice(index, 1);
+                            $clicked.addClass('disabled');
+                        }
+                        $this._inputValue(currentValue, text);
+                    });
+                }
+            } else if (text && text !== $_this.val()) {
+                $_this.val(text);
+            }
+            $this._inputValue(value, text);
+            _previousText = $this._keyword();
+            return $this;
+        }
+
+    /**
+     * Get/Set input value.
+     */
+        $this._inputValue = function (value, text) {
+            var isJSON = $this._toJSON(),
+                isCSV = $this._toCSV();
+
+            if (!_this._isDefined(value)) {
+                value = $this.val();
+                if (value) {
+                    if (isJSON) {
+                        value = JSON.parse(value);
+                    } else if (isCSV) {
+                        value = value.split(',');
+                    }
+                } else if (isJSON || isCSV) {
+                    value = [];
+                }
+                return value;
+            }
+
+            if (_this._isObject(value)) {
+                if (isJSON && !_this._isEmpty(value)) {
+                    value = JSON.stringify(value);
+                } else if (isCSV) {
+                    value = value.join(',');
+                }
+            }
+            if (value === '') {
+                $this._options('_values', []);
+            }
+            $this.val(value, true);
+            $this.trigger('change:flexdatalist', [value, text, $this._options()]).trigger('change');
+            return value;
+        }
+
+    /**
+     * Get text that will be shown to user on input field.
+     */
+        $this._getText = function (item) {
+            var text = item,
+                _options = $this._options();
+
+            if (_this._isObject(item)) {
+                text = item[_options.searchIn[0]];
+                if (_this._isDefined(item, _options.textProperty)) {
+                    text = item[_options.textProperty];
+                } else {
+                    text = $this._replacePlaceholders(item, _options.textProperty, text);
+                }
+            }
+            return $('<div>').html(text).text();
+        }
+
+    /**
+     * Get the value that will be added to hidden input.
+     * This is the value that eventually will be sent on form submittion.
+     */
+        $this._getValue = function (item) {
+            var value = item,
+                _options = $this._options();
+            if (_this._isObject(item)) {
+                value = item[_options.searchIn[0]];
+                if (_options.valueProperty === '*') {
+                    value = item;
+                } else if (_this._isDefined(item, _options.valueProperty)) {
+                    value = item[_options.valueProperty];
+                } else if ($this._toJSON()) {
+                    var value = {},
+                        properties = _options.valueProperty,
+                        textProperty = _options.textProperty;
+
+                    // Add placeholder properties to list
+                    if (textProperty) {
+                        var _properties = textProperty;
+                        if (typeof textProperty === 'string') {
+                            _properties = $this._parsePlaceholders(textProperty);
+                        }
+                        if (_this._isObject(_properties)) {
+                            $.each(_properties, function (string, property) {
+                                properties.push(property);
+                            });
+                        }
+                    } else if (_this._isDefined(item, textProperty)) {
+                        properties.push(textProperty);
+                    }
+
+                    $.each(properties, function (i, property) {
+                        if (_this._isDefined(item, property)) {
+                            value[property] = item[property];
+                        }
+                    });
+                }
+            }
+            if (_options.multiple && ($this._toJSON() || $this._toCSV())) {
+                var currentValue = $this._inputValue();
+                if (!_this._isEmpty(value) && _this._isObject(currentValue)) {
+                    currentValue.push(value);
+                    value = currentValue;
+                }
+            }
+            return value;
+        }
+
+    /**
+     * Replace placeholders ('{property_name}') in text
+     * with respective property value.
+     */
+        $this._replacePlaceholders = function (item, pattern, value) {
+            if (_this._isObject(item) && typeof pattern === 'string') {
+                var properties = $this._parsePlaceholders(pattern);
+                if (!_this._isEmpty(item) && properties) {
+                    $.each(properties, function (string, property) {
+                        if (_this._isDefined(item, property)) {
+                            pattern = pattern.replace(string, item[property]);
+                        }
+                    });
+                    return pattern;
+                }
+            }
+            return value;
+        }
+
+    /**
+     * Extract placeholders property names.
+     */
+        $this._parsePlaceholders = function (pattern) {
+            var matches = pattern.match(/\{.+?\}/g);
+            if (matches) {
+                var properties = {};
+                matches.map(function (string) {
+                    properties[string] = string.slice(1, -1);
+                });
+                return properties;
+            }
+            return false;
+        }
+
+    /**
+     * Normalize string to a consistent one to perform the search/match.
+     */
+        $this._normalizeString = function (string) {
+            if (typeof string === 'string') {
+                var normalizeString = $this._options('normalizeString');
+                if (typeof normalizeString === 'function') {
+                    string = normalizeString(string);
+                }
+                return string.toUpperCase();
+            }
+            return string;
+        }
+
+    /**
+     * Get keyword with left trim.
+     */
+        $this._keyword = function () {
+            return $_this.val().replace(/^\s+/, "");
+        }
+
+    /**
+     * Check if input value must be a JSON string.
+     */
+        $this._toJSON = function () {
+            var valueProperty = $this._options('valueProperty');
+            return _this._isObject(valueProperty) || valueProperty === '*';
+        }
+
+    /**
+     * Check if input value must be a CSV string.
+     */
+        $this._toCSV = function () {
+            return (!$this._toJSON() && $this._options('multiple'));
+        }
+
+    /**
+     * Position results below parent element.
+     */
+        $this._position = function () {
+            var $target = $_this;
+            if ($this._options('multiple')) {
+                $target = $ulMultiple;
+            }
+            // Set some required CSS properties
+            $('ul.flexdatalist-results').css({
+                'width': $target.outerWidth() + 'px',
+                'top': (($target.offset().top + $target.outerHeight())) + 'px',
+                'left': $target.offset().left + 'px',
+                'z-index': ($target.css('z-index') + 1)
+            });
+        }
+        // Set datalist data
+        $this._datalist();
+        // Process default value
+        $this._initValue();
+        // Handle chained fields
+        $this._chained();
+        // Initialize
+        $this._init();
+    };
+
+/**
+ * Destroy.
+ */
+    this._destroy = function ($_input) {
+        if (!$_input) {
+            $_input = $input;
+        }
+        $_input.each(function () {
+            var data = $(this).data('flexdatalist');
+            $(this).removeClass('flexdatalist-set')
+                .attr('type', 'text')
+                .val((data && data.originalValue ? data.originalValue : ''))
+                .data('flexdatalist', null)
+                .next('.flexdatalist-alias, ul.flexdatalist-multiple')
+                .remove();
+        });
+    }
+
+/**
+ * Reset.
+ */
+    this._reset = function () {
+        this._destroy();
+    }
+
+/**
+ * Get key code from event.
+ */
+    this._keyNum = function (event) {
+        return event.which || event.keyCode;
+    }
+
+    // Handle selection list keyboard shortcuts and events.
+    if (!$document.data('flexdatalist')) {
+        // Remove results on outside click
+        $(document).mouseup(function (event) {
+            var $container = $('.flexdatalist-results'),
+                $target = $container.data('target');
+            if ((!$target || !$target.is(':focus')) && !$container.is(event.target) && $container.has(event.target).length === 0) {
+                $container.remove();
+            }
+        // Keyboard navigation
+        }).keydown(function (event) {
+            var $ul = $('.flexdatalist-results'),
+                $li = $ul.find('li'),
+                $active = $li.filter('.active'),
+                index = $active.index(),
+                length = $li.length,
+                keynum = _this._keyNum(event);
+
+            if (length === 0) {
+                return;
+            }
+
+            // Enter key
+            if (keynum === 13) {
+                event.preventDefault();
+                $active.click();
+            // Up/Down key
+            } else if (keynum === 40 || keynum === 38) {
+                event.preventDefault();
+                // Down key
+                if (keynum === 40) {
+                    if (index < length && $active.nextAll('.item').first().length > 0) {
+                        $active = $active.removeClass('active').nextAll('.item').first().addClass('active');
+                    } else {
+                        $active = $li.removeClass('active').filter('.item:first').addClass('active');
+                    }
+                // Up key
+                } else if (keynum === 38) {
+                    if (index > 0 && $active.prevAll('.item').first().length > 0) {
+                        $active = $active.removeClass('active').prevAll('.item').first().addClass('active');
+                    } else {
+                        $active = $li.removeClass('active').filter('.item:last').addClass('active');
+                    }
+                }
+
+                // Scroll to
+                var position = ($active.prev().length === 0 ? $active : $active.prev()).position().top;
+                $ul.animate({
+                    scrollTop: position + $ul.scrollTop()
+                }, 100);
+            }
+        }).data('flexdatalist', true);
+    }
+
+/**
+ * Is variable empty.
+ */
+    this._isEmpty = function (value) {
+        if (!_this._isDefined(value)) {
+            return true;
+        } else if (value === null) {
+            return true;
+        } else if (value === true) {
+            return false;
+        } else if (this._length(value) === 0) {
+            return true;
+        } else if ($.trim(value) === '') {
+            return true;
+        }
+        return false;
+    }
+
+/**
+ * Is variable an object.
+ */
+    this._isObject = function (value) {
+        return (value && typeof value === 'object');
+    }
+
+/**
+ * To array.
+ */
+    this._csvToArray = function (value, _default) {
+        if (value.length === 0) {
+            return _default;
+        }
+        return typeof value === 'string' ? value.split(',') : value;
+    }
+
+/**
+ * Get length of variable.
+ */
+    this._length = function (value) {
+        if (this._isObject(value)) {
+            return Object.keys(value).length;
+        } else if (typeof value.length === 'number') {
+            return value.length;
+        }
+        return 0;
+    }
+/**
+ * Check if variable (and optionally property) is defined.
+ */
+    this._isDefined = function (variable, property) {
+        var _variable = (typeof variable !== 'undefined');
+        if (_variable && typeof property !== 'undefined') {
+            return (typeof variable[property] !== 'undefined');
+        }
+        return _variable;
+    }
+
+/**
+ * Handle options.
+ */
+    if (typeof options === 'string') {
+        if (typeof this['_' + options] === 'function') {
+            if (!this['_' + options]()) {
+                return this;
+            }
+        } else if (!value) {
+            var _data = $input.data('flexdatalist');
+            return _data[options];
+        // set value programmatically
+        } else if (options === 'value') {
+            var _data = $input.data('flexdatalist');
+            _data['originalValue'] = value;
+            _this._destroy();
+            $input.data('flexdatalist', _data);
+        } else {
+            var _data = $input.data('flexdatalist');
+            _data[options] = value;
+            $input.data('flexdatalist', _data);
+            return this;
+        }
+    }
+
+    return this.each(input);
+}
+
+var _defaultValFunc = jQuery.fn.val;
+jQuery.fn.val = function (value, _flexdatalist) {
+    if (!_flexdatalist && $(this).hasClass('flexdatalist-set') && typeof value !== 'undefined') {
+        $(this).flexdatalist('value', value);
+    }
+    return _defaultValFunc.apply(this, arguments);
+};
+
+$(function () {
+    $('input.flexdatalist:not(.flexdatalist-set)').flexdatalist();
+});
\ No newline at end of file
diff --git a/webroot/asset/flexdatalist/jquery.flexdatalist.min.css b/webroot/asset/flexdatalist/jquery.flexdatalist.min.css
new file mode 100644 (file)
index 0000000..1d58ce6
--- /dev/null
@@ -0,0 +1 @@
+.flexdatalist-results{position:absolute;top:0;left:0;border:1px solid #444;border-top:none;background:#fff;z-index:100000;max-height:300px;overflow-y:auto;box-shadow:0 4px 5px rgba(0,0,0,.15);color:#333;list-style:none;margin:0;padding:0}.flexdatalist-results li{border-bottom:1px solid #ccc;padding:0 15px;font-size:14px;line-height:35px}.flexdatalist-results li span.highlight{font-weight:700;text-decoration:underline}.flexdatalist-results li.active{background:#2B82C9;color:#fff;cursor:pointer}.flexdatalist-results li.no-results{font-style:italic;color:#888}.flexdatalist-results li.group{background:#F3F3F4;color:#666;padding:0 8px}.flexdatalist-results li .group-name{font-weight:700}.flexdatalist-results li .group-item-count{font-size:85%;color:#777;display:inline-block;padding-left:10px}.flexdatalist-multiple:after,.flexdatalist-multiple:before{content:'';display:block;clear:both}.flexdatalist-multiple{width:100%;margin:0;padding:0 0 0 10px;list-style:none;text-align:left;cursor:text}.flexdatalist-multiple li{display:inline-block;position:relative;margin:5px 5px 5px 0;float:left}.flexdatalist-multiple li.input-container,.flexdatalist-multiple li.input-container input{border:none;width:280px;height:auto;padding:0;line-height:25px}.flexdatalist-multiple li.value{display:inline-block;padding:2px 25px 2px 5px;background:#efefef;border-radius:3px}.flexdatalist-multiple li.toggle{cursor:pointer;transition:opacity ease-in-out .3s}.flexdatalist-multiple li.toggle.disabled{text-decoration:line-through;opacity:.8}.flexdatalist-multiple li.value span.fdl-remove{font-weight:700;padding:0 5px;font-size:20px;line-height:25px;cursor:pointer;position:absolute;top:0;right:0;opacity:.7}.flexdatalist-multiple li.value span.fdl-remove:hover{opacity:1}
\ No newline at end of file
diff --git a/webroot/asset/flexdatalist/jquery.flexdatalist.min.js b/webroot/asset/flexdatalist/jquery.flexdatalist.min.js
new file mode 100644 (file)
index 0000000..7e42777
--- /dev/null
@@ -0,0 +1 @@
+jQuery.fn.flexdatalist=function(e,t){"use strict";var a,i=$(document),s=$(this),r=this;if(a=function(){var t=$(this),a={},i="",s=null;t.attr("name");t.hasClass("flexdatalist-set")&&r._destroy(t),t._options=function(e,a){var i=t.data("flexdatalist");if(!r._isDefined(e))return t.data("flexdatalist");if(r._isDefined(a))i[e]=a;else{if(!r._isObject(e))return r._isDefined(i,e)?i[e]:null;i=e}return i.searchIn=r._csvToArray(i.searchIn),i.relatives=i.relatives&&$(i.relatives).length>0?$(i.relatives):null,i.textProperty=null===i.textProperty?i.searchIn[0]:i.textProperty,i.visibleProperties=r._csvToArray(i.visibleProperties,i.searchIn),t.data("flexdatalist",i),t},t._options($.extend({url:null,data:[],params:{},relatives:null,chainedRelatives:!1,cache:!0,minLength:2,groupBy:!1,selectionRequired:!1,focusFirstResult:!1,textProperty:null,valueProperty:null,visibleProperties:[],searchIn:["label"],searchContain:!1,searchEqual:!1,searchDisabled:!1,normalizeString:null,multiple:t.attr("multiple"),maxShownResults:100,noResultsText:'No results found for "{keyword}"',toggleSelected:!1,_values:[]},e,t.data()));var l=t.clone(!1).attr({list:null,name:null,id:t.attr("id")?t.attr("id")+"-flexdatalist":null}).addClass("flexdatalist-alias").removeClass("flexdatalist");if(t._options("multiple")){var n=$("<ul>").addClass("flexdatalist-multiple").css({"background-color":t.css("background-color"),"border-color":t.css("border-left-color"),"border-width":t.css("border-left-width"),"border-style":t.css("border-left-style"),"border-radius":t.css("border-top-left-radius")}).insertAfter(t).click(function(){$(this).find("input").focus()});$('<li class="input-container">').addClass("flexdatalist-multiple-value").append(l).appendTo(n)}else l.insertAfter(t);t.addClass("flexdatalist").attr("type","hidden"),t._init=function(){var e=t._options();l.on("input keydown",function(a){var i=t._keyword();188===r._keyNum(a)&&!e.selectionRequired&&e.multiple?(a.preventDefault(),t._value(i),t._removeResults()):9===r._keyNum(a)?t._removeResults():0===i.length&&e.multiple&&8===r._keyNum(a)&&l.data("_remove",l.parents("li:eq(0)").prev())}).on("input keyup",function(a){if(t._changed()&&13!==r._keyNum(a)){var s=t._keyword();s.length>=e.minLength?t._search(function(e){t._showResults(e)}):t._removeResults(),e.multiple||(e.selectionRequired?t._value(""):t._value(s))}var n=l.data("_remove");n&&(n.find(".fdl-remove").click(),l.data("_remove",null)),i=t._keyword()}).focus(function(){var a=t._keyword();0===e.minLength?""===a&&t._tdata(function(e){t._showResults(e)}):a.length>=e.minLength&&!t._selected()&&t._search(function(e){t._showResults(e)})}).attr("autocomplete","off"),l.attr("autofocus")&&l.focus(),window.onresize=function(e){t._position()},t.addClass("flexdatalist-set")},t._changed=function(){return i!==t._keyword()},t._chained=function(){var e=t._options();if(e.relatives&&e.chainedRelatives){var i=function(a){e.relatives.each(function(){var i=r._isEmpty($(this).val()),s=r._isEmpty(t.val());l.prop("disabled",i),a||!i&&s||(t._value(""),l.val(""),e.multiple&&n.find("li .remove").click())})};e.relatives.on("change",function(){i(),a={}}),i(!0)}},t._initValue=function(){var e=t.attr("value");r._isEmpty(e)||(t._options("originalValue",t.val()),t._parseValue(e,function(e){t.val("",!0),l.val(""),r._isEmpty(e)||t._values(e),i=t._keyword()}))},t._parseValue=function(e,a){var i=t._options();if(t._toJSON())try{a(JSON.parse(e))}catch(s){}else if(t._toCSV()||"string"==typeof i.valueProperty){var r=e.split(",");if("string"==typeof i.valueProperty){var l=i.searchIn;i.searchIn=i.valueProperty.split(","),i.searchEqual=!0,t._search(function(e){e.length>0&&a(e),i.searchIn=l,i.searchEqual=!1},r)}else a(r)}else a(e)},t._tdata=function(e){t.trigger("before:flexdatalist.data"),t._url(function(a){t._data(function(i){i=i.concat(a);for(var s=t._options("_values"),r=0;r<i.length;r++){var l=i[r];s&&s.indexOf(t._getText(l))>-1&&delete i[r]}t.trigger("after:flexdatalist.data",[i]),e(i)})})},t._data=function(e){var a=t._options();"string"==typeof a.data?t._remote({url:a.data,success:function(i){var s=t._getRemoteData(i);a.data=s,e(s)}}):e(a.data)},t._url=function(e){var a=t._options(),i=t._keyword(),l=t.val(),n=i;return r._isEmpty(a.url)?e([]):(clearTimeout(s),void(s=setTimeout(function(){a.cache&&2!==a.cache&&(n=i.substring(0,a.minLength>0?a.minLength:1));var s=t._cache(n);return s?void e(s):void t._remote({url:a.url,data:$.extend(t._relativesData(),a.params,{keyword:i,contain:a.searchContain,selected:l}),success:function(a){var s=t._getRemoteData(a),r=t._keyword();r.length>i.length?t._search(function(e){t._showResults(e)}):e(s),t._cache(n,s)}})},200)))},t._remote=function(e){t.hasClass("flexdatalist-loading")||(t.addClass("flexdatalist-loading"),e=$.extend({type:"post",dataType:"json",complete:function(){t.removeClass("flexdatalist-loading")}},e),$.ajax(e))},t._getRemoteData=function(e){var t=e.results?e.results:e;return"string"==typeof t&&0===t.indexOf("[{")&&(t=JSON.parse(t)),r._isObject(t)?t:[]},t._relativesData=function(){var e=t._options("relatives"),a={};return e&&(a.relatives={},e.each(function(){var e=$(this);a.relatives[e.attr("name")]=e.val()})),a},t._datalist=function(){var e=t._options(),a=t.attr("list");return r._isEmpty(a)||(e.data=[],$("#"+a).find("option").each(function(){var t=$(this).val();e.data.push({label:t,value:t})})),t},t._cache=function(e,i){if(t._options("cache")){if(e=t._normalizeString(e),!r._isDefined(i))return r._isDefined(a,e)&&(i=a[e]),i;a[e]=i}return null},t._search=function(e,a){t._tdata(function(i){var s=[],l=t._options();if(l.searchDisabled)return e(i);r._isDefined(a)||(a=t._keyword()),"string"==typeof a&&(a=[a]),t.trigger("before:flexdatalist.search",[a,i]);for(var n=0;n<a.length;n++)for(var o=a[n],u=0;u<i.length;u++){var d=t._matches(i[u],o,l.values);d&&s.push(d)}t.trigger("after:flexdatalist.search",[a,i,s]),e(s)})},t._matches=function(e,a,i){for(var s=!1,l=$.extend({},e),n=t._options(),o=n.searchIn,u=0;u<o.length;u++){var d=o[u];if(r._isDefined(e,d)&&e[d]){var f=e[d].toString();t._find(a,f)&&(l[d+"_highlight"]=t._highlight(a,f),s=!0)}}return s?l:null},t._highlight=function(e,a){return a.replace(new RegExp(e,t._options("searchContain")?"ig":"i"),'<span class="highlight">$&</span>')},t._find=function(e,a){var i=t._options();return a=t._normalizeString(a),e=t._normalizeString(e),i.searchEqual?a==e:i.searchContain?a.indexOf(e)>=0:0===a.indexOf(e)},t._showResults=function(e){t._removeResults(!0);var a=t._options();if(0===e.length){var i=t._keyword();e=[{label:i,value:i,label_highlight:'<span class="highlight">'+i+"</span>"}]}var s=t._getResultsContainer();a.groupBy?(e=t._groupData(e),Object.keys(e).forEach(function(i,r){var l=e[i],n=a.groupBy,o=t._getHighlight(l[0],n,i);$("<li>").addClass("group").append($("<span>").addClass("group-name").html(o)).append($("<span>").addClass("group-item-count").text(" "+l.length)).appendTo(s);t._items(l,s)})):(console.log(e),t._items(e,s));var r=s.find("li:not(.group)");r.on("click",function(e){var i=$(this).data("item");i&&(t._selected(!0)._removeResults()._value(i),t.trigger("select:flexdatalist",[i,a]))}).hover(function(){r.removeClass("active"),$(this).addClass("active")},function(){$(this).removeClass("active")}),a.focusFirstResult&&r.filter(":first").addClass("active")},t._noResults=function(e){if(!r._isEmpty(e)){var a=t._getResultsContainer(),i=t._keyword();e=e.split("{keyword}").join(i),$("<li>").addClass("item no-results").append(e).appendTo(a)}},t._groupData=function(e){for(var a=[],i=t._options("groupBy"),s=0;s<e.length;s++){var l=e[s];if(r._isDefined(l,i)){var n=l[i];r._isDefined(a,n)||(a[n]=[]),a[n].push(l)}}return a},t._items=function(e,a){var i=t._options("maxShownResults");t.trigger("show:flexdatalist.results",[e]);for(var s=0;s<e.length&&!(i>0&&i===s);s++)t._item(e[s]).appendTo(a);t.trigger("shown:flexdatalist.results",[e])},t._item=function(e){for(var a=$("<li>").data("item",e).addClass("item"),i=t._options(),s=i.visibleProperties,l=0;l<s.length;l++){var n=s[l];if((!i.groupBy||i.groupBy!==n)&&r._isDefined(e,n)){var o={};if("thumb"===n)o=$("<img>").addClass("item item-"+n).attr("src",e[n]);else{var u=t._getHighlight(e,n);o=$("<span>").addClass("item item-"+n).html(u+" ")}o.appendTo(a)}}return a},t._getHighlight=function(e,t,a){return r._isDefined(e,t+"_highlight")?e[t+"_highlight"]:r._isDefined(e,t)?e[t]:a},t._getResultsContainer=function(){var e=t;t._options("multiple")&&(e=n);var a=$("ul.flexdatalist-results");return 0===a.length&&(a=$("<ul>").addClass("flexdatalist-results").appendTo("body").css({"border-color":e.css("border-left-color"),"border-width":"1px","border-bottom-left-radius":e.css("border-bottom-left-radius"),"border-bottom-right-radius":e.css("border-bottom-right-radius")}).data("target",l),t._position()),a},t._removeResults=function(e){var a="ul.flexdatalist-results";return e&&(a="ul.flexdatalist-results li"),$(a).remove(),t},t._selected=function(e){var a="flexdatalist-selected";return r._isDefined(e)?(e?t.addClass(a):t.removeClass(a),t):t.hasClass(a)},t._values=function(e){return $.isArray(e)&&!r._isEmpty(e)?void $.each(e,function(e,a){t._value(a)}):void t._value(e)},t._value=function(e){var a=t._options(),s=t._getText(e),r=t._getValue(e);if(s.length>0&&a._values.push(s),a.multiple){if(""===e)return t;l.val("");var o=$("<li>").addClass("value"+(a.toggleSelected?" toggle":"")).append('<span class="text">'+s+"</span>").append('<span class="fdl-remove">&times;</span>').insertBefore(n.find("li.input-container"));o.find("span.fdl-remove").click(function(){var e=$(this).parent(),i=e.index();if(!e.hasClass("disabled")&&(t._toJSON()||t._toCSV())){var s=t._inputValue();s.splice(i,1),a._values.splice(i,1),t._inputValue(s)}e.remove()}),a.toggleSelected&&o.click(function(){var e=$(this),i=t._inputValue(),r=e.index();if(e.hasClass("disabled")){var l=e.data("_value");i.splice(r,0,l),a._values.splice(r,0,t._getText(l)),e.removeClass("disabled")}else{var l=i.splice(r,1);e.data("_value",l[0]),a._values.splice(r,1),e.addClass("disabled")}t._inputValue(i,s)})}else s&&s!==l.val()&&l.val(s);return t._inputValue(r,s),i=t._keyword(),t},t._inputValue=function(e,a){var i=t._toJSON(),s=t._toCSV();return r._isDefined(e)?(r._isObject(e)&&(i&&!r._isEmpty(e)?e=JSON.stringify(e):s&&(e=e.join(","))),""===e&&t._options("_values",[]),t.val(e,!0),t.trigger("change:flexdatalist",[e,a,t._options()]).trigger("change"),e):(e=t.val(),e?i?e=JSON.parse(e):s&&(e=e.split(",")):(i||s)&&(e=[]),e)},t._getText=function(e){var a=e,i=t._options();return r._isObject(e)&&(a=e[i.searchIn[0]],a=r._isDefined(e,i.textProperty)?e[i.textProperty]:t._replacePlaceholders(e,i.textProperty,a)),$("<div>").html(a).text()},t._getValue=function(e){var a=e,i=t._options();if(r._isObject(e))if(a=e[i.searchIn[0]],"*"===i.valueProperty)a=e;else if(r._isDefined(e,i.valueProperty))a=e[i.valueProperty];else if(t._toJSON()){var a={},s=i.valueProperty,l=i.textProperty;if(l){var n=l;"string"==typeof l&&(n=t._parsePlaceholders(l)),r._isObject(n)&&$.each(n,function(e,t){s.push(t)})}else r._isDefined(e,l)&&s.push(l);$.each(s,function(t,i){r._isDefined(e,i)&&(a[i]=e[i])})}if(i.multiple&&(t._toJSON()||t._toCSV())){var o=t._inputValue();!r._isEmpty(a)&&r._isObject(o)&&(o.push(a),a=o)}return a},t._replacePlaceholders=function(e,a,i){if(r._isObject(e)&&"string"==typeof a){var s=t._parsePlaceholders(a);if(!r._isEmpty(e)&&s)return $.each(s,function(t,i){r._isDefined(e,i)&&(a=a.replace(t,e[i]))}),a}return i},t._parsePlaceholders=function(e){var t=e.match(/\{.+?\}/g);if(t){var a={};return t.map(function(e){a[e]=e.slice(1,-1)}),a}return!1},t._normalizeString=function(e){if("string"==typeof e){var a=t._options("normalizeString");return"function"==typeof a&&(e=a(e)),e.toUpperCase()}return e},t._keyword=function(){return l.val().replace(/^\s+/,"")},t._toJSON=function(){var e=t._options("valueProperty");return r._isObject(e)||"*"===e},t._toCSV=function(){return!t._toJSON()&&t._options("multiple")},t._position=function(){var e=l;t._options("multiple")&&(e=n),$("ul.flexdatalist-results").css({width:e.outerWidth()+"px",top:e.offset().top+e.outerHeight()+"px",left:e.offset().left+"px","z-index":e.css("z-index")+1})},t._datalist(),t._initValue(),t._chained(),t._init()},this._destroy=function(e){e||(e=s),e.each(function(){var e=$(this).data("flexdatalist");$(this).removeClass("flexdatalist-set").attr("type","text").val(e&&e.originalValue?e.originalValue:"").data("flexdatalist",null).next(".flexdatalist-alias, ul.flexdatalist-multiple").remove()})},this._reset=function(){this._destroy()},this._keyNum=function(e){return e.which||e.keyCode},i.data("flexdatalist")||$(document).mouseup(function(e){var t=$(".flexdatalist-results"),a=t.data("target");a&&a.is(":focus")||t.is(e.target)||0!==t.has(e.target).length||t.remove()}).keydown(function(e){var t=$(".flexdatalist-results"),a=t.find("li"),i=a.filter(".active"),s=i.index(),l=a.length,n=r._keyNum(e);if(0!==l)if(13===n)e.preventDefault(),i.click();else if(40===n||38===n){e.preventDefault(),40===n?i=l>s&&i.nextAll(".item").first().length>0?i.removeClass("active").nextAll(".item").first().addClass("active"):a.removeClass("active").filter(".item:first").addClass("active"):38===n&&(i=s>0&&i.prevAll(".item").first().length>0?i.removeClass("active").prevAll(".item").first().addClass("active"):a.removeClass("active").filter(".item:last").addClass("active"));var o=(0===i.prev().length?i:i.prev()).position().top;t.animate({scrollTop:o+t.scrollTop()},100)}}).data("flexdatalist",!0),this._isEmpty=function(e){return r._isDefined(e)?null===e?!0:e===!0?!1:0===this._length(e)?!0:""===$.trim(e)?!0:!1:!0},this._isObject=function(e){return e&&"object"==typeof e},this._csvToArray=function(e,t){return 0===e.length?t:"string"==typeof e?e.split(","):e},this._length=function(e){return this._isObject(e)?Object.keys(e).length:"number"==typeof e.length?e.length:0},this._isDefined=function(e,t){var a="undefined"!=typeof e;return a&&"undefined"!=typeof t?"undefined"!=typeof e[t]:a},"string"==typeof e)if("function"==typeof this["_"+e]){if(!this["_"+e]())return this}else{if(!t){var l=s.data("flexdatalist");return l[e]}if("value"!==e){var l=s.data("flexdatalist");return l[e]=t,s.data("flexdatalist",l),this}var l=s.data("flexdatalist");l.originalValue=t,r._destroy(),s.data("flexdatalist",l)}return this.each(a)};var _defaultValFunc=jQuery.fn.val;jQuery.fn.val=function(e,t){return!t&&$(this).hasClass("flexdatalist-set")&&"undefined"!=typeof e&&$(this).flexdatalist("value",e),_defaultValFunc.apply(this,arguments)},$(function(){$("input.flexdatalist:not(.flexdatalist-set)").flexdatalist()});
\ No newline at end of file
diff --git a/webroot/asset/img/insipid.png b/webroot/asset/img/insipid.png
new file mode 100644 (file)
index 0000000..9a374a0
Binary files /dev/null and b/webroot/asset/img/insipid.png differ
index 7567c906b30deda9b015d18150d8a7c689ec89bc..c805b01d69454fae18cd92e3bdab8ef98a5c9775 100644 (file)
@@ -77,6 +77,22 @@ class Management {
         return $ret;
     }
 
+    /**
+     * return the latest addded links
+     * @param number $limit
+     */
+    public function latest($limit=5) {
+        $ret = array();
+
+        $queryStr = "SELECT * FROM `".DB_PREFIX."_link` WHERE `status` = 2 ORDER BY `created` DESC";
+        $query = $this->DB->query($queryStr);
+        if(!empty($query) && $query->num_rows > 0) {
+            $ret = $query->fetch_all(MYSQLI_ASSOC);
+        }
+
+        return $ret;
+    }
+
 }
 
 ?>
\ No newline at end of file
index d429468760cb678d7e7bffe254f440d483bd4182..92445bf563f4e18a09842b26d918f01abfdddf76 100644 (file)
@@ -39,6 +39,7 @@
        <script src="asset/js/jquery.js"></script>
     <script src="asset/js/what-input.js"></script>
     <script src="asset/js/foundation.min.js"></script>
+    <script src="asset/flexdatalist/jquery.flexdatalist.min.js"></script>
     <script src="asset/js/app.js"></script>
   </body>
 </html>
index 3b67ef616f3aa0248da05096ae593a3eed4fc285..4a4761c7c58b591c5897d442be5c14c0121ac39f 100644 (file)
@@ -35,6 +35,7 @@
     <title>Insipid</title>
     <link rel="stylesheet" href="asset/css/foundation.min.css">
     <link rel="stylesheet" href="asset/foundation-icons/foundation-icons.css">
+    <link rel="stylesheet" href="asset/flexdatalist/jquery.flexdatalist.min.css">
     <link rel="stylesheet" href="asset/css/app.css">
   </head>
   <body>
\ No newline at end of file
index 6a40d966b4deed2dac34295413e4407c26271527..3bc26f70b63317aab91d23a7cb5c1dcd6e26fb4d 100644 (file)
@@ -70,9 +70,15 @@ if(isset($_POST['data']) && !empty($_POST['data']) && isset($_POST['submitsearch
         # try to gather some information automatically
         $linkInfo = Summoner::gatherInfoFromURL($searchValue);
         if(!empty($linkInfo)) {
-            $formData['description'] = $linkInfo['description'];
-            $formData['title'] = $linkInfo['title'];
-            $formData['image'] = $linkInfo['image'];
+            if(isset($linkInfo['description'])) {
+                $formData['description'] = $linkInfo['description'];
+            }
+            if(isset($linkInfo['title'])) {
+                $formData['title'] = $linkInfo['title'];
+            }
+            if(isset($linkInfo['image'])) {
+                $formData['image'] = $linkInfo['image'];
+            }
         }
         # show the add form
         $showAddForm = true;
@@ -120,8 +126,6 @@ if(isset($_POST['data']) && !empty($_POST['data']) && isset($_POST['addnewone'])
         $DB->query($queryStr);
         $linkID = $DB->insert_id;
 
-        var_dump($linkID);
-
         if(!empty($linkID)) {
 
             # categories and tag stuff
@@ -165,4 +169,5 @@ if(isset($_POST['data']) && !empty($_POST['data']) && isset($_POST['addnewone'])
 }
 
 $existingCategories = $Management->categories();
-$existingTags = $Management->tags();
\ No newline at end of file
+$existingTags = $Management->tags();
+$latestLinks = $Management->latest();
\ No newline at end of file
index 751f1450817000359101a65cf93ab4e66b853f54..38ce572b4811e7970cd886b30b627120b5a21d44 100644 (file)
 </div>
 <?php } ?>
 
+<?php if(!empty($searchResult)) { ?>
+<div class="row">
+       <div class="large-12 columns">
+               <h3>Something has been found...</h3>
+               <ul>
+<?php foreach ($searchResult as $sr) { ?>
+               <li>
+                       <a href="<?php echo $sr['link']; ?>" target="_blank" ><?php echo $sr['title']; ?></a>
+                       <a href="<?php echo $sr['link']; ?>" ><i class="fi-info"></i></a>
+               </li>
+<?php } ?>
+               </ul>
+       </div>
+</div>
+<?php } ?>
+
 <?php if($showAddForm) { ?>
 <form method="post">
        <input type="hidden" name="password" />
        <div class="large-6 columns">
                <label>
                        Category
-                       <input type="text" name="data[category]" list="categorylist" value="<?php echo Summoner::ifset($formData, 'category'); ?>" />
+                       <input type="text" name="data[category]" list="categorylist"
+                               class="flexdatalist" data-min-length='1' multiple='multiple'
+                               value="<?php echo Summoner::ifset($formData, 'category'); ?>" />
                        <datalist id="categorylist">
                                <?php foreach($existingCategories as $c) { ?>
-                                       <option value="<?php echo $c; ?>">
+                                       <option value="<?php echo $c['name']; ?>">
                                <?php } ?>
                 </datalist>
                </label>
        <div class="large-6 columns">
                <label>
                        Tag
-                       <input type="text" name="data[tag]" list="taglist" value="<?php echo Summoner::ifset($formData, 'tag'); ?>" />
+                       <input type="text" name="data[tag]" list="taglist"
+                               class="flexdatalist" data-min-length='1' multiple='multiple'
+                               value="<?php echo Summoner::ifset($formData, 'tag'); ?>" />
                        <datalist id="taglist">
                        <?php foreach($existingTags as $t) { ?>
-                                       <option value="<?php echo $t; ?>">
+                                       <option value="<?php echo $t['name']; ?>">
                                <?php } ?>
                 </datalist>
                </label>
                        <div class="card-divider">
                        <h4>Last added</h4>
                        </div>
-                       <img src="assets/img/generic/rectangle-1.jpg">
+                       <img src="asset/img/insipid.png">
                        <div class="card-section">
-                               <p>It has an easy to override visual style, and is appropriately subdued.</p>
-                               <a class="button" href="#">I'm a button</a>
+<?php if(!empty($latestLinks)) { ?>
+                               <ul>
+<?php foreach ($latestLinks as $ll) { ?>
+                                       <li>
+                                               <a href="<?php echo $ll['link']; ?>" target="_blank"><?php echo $ll['title']; ?></a>
+                                       </li>
+<?php } ?>
+                               </ul>
+                               <a class="button" href="#">more</a>
+<?php } ?>
                        </div>
                </div>
        </div>