diff --git a/Gruntfile.js b/Gruntfile.js index 332d0474a865dbe0ff55fa0ae7991501e8f1ee58..7c54f2d4b9f04b2eb72096f48b4f488c21d95296 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -304,6 +304,28 @@ module.exports = function (grunt) { flatten: true } ] + }, + typeahead: { + files: [ + { + cwd: 'bower_components/corejs-typeahead/dist', + src: [ + "typeahead.bundle.min.js" + ], + expand: true, + dest: typo3_js, + flatten: true + }, + { + cwd: 'bower_components/corejs-typeahead/dist', + src: [ + "typeahead.bundle.min.js" + ], + expand: true, + dest: 'js/', + flatten: true + } + ] } }, uglify: { diff --git a/bower.json b/bower.json index 249fe0462c3892ae6d52ab1427bccd2f7ec6c0c2..46da5a2c5d61f1d9c620ef3de0f0e1610229a250 100644 --- a/bower.json +++ b/bower.json @@ -23,6 +23,7 @@ "eventEmitter": "^4.3.0", "bootstrap-validator": "^0.11.5", "Chart.js": "^2.1.2", - "tinymce": "tinymce-dist#^4.4.3" + "tinymce": "tinymce-dist#^4.4.3", + "corejs-typeahead": "^1.1.1" } } diff --git a/javascript/src/QfqPage.js b/javascript/src/QfqPage.js index 340fb0f081b30e9a50e6aaed1e14083f08f0e512..cef6916b4e3a4072d721f4f44f793cea6a1949b2 100644 --- a/javascript/src/QfqPage.js +++ b/javascript/src/QfqPage.js @@ -33,6 +33,7 @@ var QfqNS = QfqNS || {}; refreshUrl: "typo3conf/ext/qfq/qfq/api/load.php", fileUploadTo: "typo3conf/ext/qfq/qfq/api/upload.php", fileDeleteUrl: "typo3conf/ext/qfq/qfq/api/filedelete.php", + typeAheadUrl: "typo3conf/ext/qfq/qfq/api/typeahead.php", pageState: new n.PageState() }, settings ); @@ -78,6 +79,8 @@ var QfqNS = QfqNS || {}; n.Log.error(e.message); this.qfqForm = null; } + + QfqNS.TypeAhead.install(this.settings.typeAheadUrl); }; /** diff --git a/javascript/src/TypeAhead.js b/javascript/src/TypeAhead.js new file mode 100644 index 0000000000000000000000000000000000000000..3ba5bca39af5ea4ad0402e917b6deea291fc1f0a --- /dev/null +++ b/javascript/src/TypeAhead.js @@ -0,0 +1,116 @@ +/** + * @author Rafael Ostertag <rafael.ostertag@math.uzh.ch> + */ + +/* global $ */ +/* global console */ +/* global Bloodhound */ +/* global Math */ + +/* @depend Utils.js */ + +var QfqNS = QfqNS || {}; + +(function (n) { + 'use strict'; + + n.TypeAhead = {}; + + + /** + * + * @param typeahead_endpoint the endpoint to be called + * @constructor + */ + n.TypeAhead.install = function (typeahead_endpoint) { + + $('.qfq-type-ahead').each(function () { + var $element = $(this); + var suggestions = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('key', 'value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + identify: function (obj) { + return obj.key; + }, + remote: { + url: n.TypeAhead.makeUrl(typeahead_endpoint, $element), + wildcard: '%QUERY' + } + }); + + n.TypeAhead.makeShadowElement($element); + + $element.typeahead({ + hint: n.TypeAhead.getHint($element), + highlight: n.TypeAhead.getHighlight($element), + minLength: n.TypeAhead.getMinLength($element) + }, + { + display: 'value', + source: suggestions, + templates: { + suggestion: function (obj) { + return "<div>" + n.TypeAhead.htmlEncode(obj.value) + "</div>"; + }, + notFound: function (obj) { + return "<div>'" + n.TypeAhead.htmlEncode(obj.query) + "' not found"; + } + } + }); + $element.bind('typeahead:select typeahead:autocomplete', function (event, suggestion) { + var $shadowElement = $(event.delegateTarget).data('shadow-element'); + $shadowElement.val(suggestion.key); + }); + }); + + }; + + n.TypeAhead.makeUrl = function (endpoint, element) { + return endpoint + "?query=%QUERY" + "&sip=" + n.TypeAhead.getSip(element); + }; + + n.TypeAhead.getSip = function ($element) { + return $element.data('sip'); + }; + + n.TypeAhead.getName = function ($element) { + return $element.attr('name'); + }; + + n.TypeAhead.getValue = function ($element) { + return $element.val(); + }; + + n.TypeAhead.getMinLength = function ($element) { + return $element.data('ta-min-length') || 2; + }; + + n.TypeAhead.getHighlight = function ($element) { + return $element.data('ta-higlight') || true; + }; + + n.TypeAhead.getHint = function ($element) { + return $element.data('ta-hint') || true; + }; + + n.TypeAhead.htmlEncode = function (value) { + return $('<div/>').text(value).html(); + }; + + n.TypeAhead.makeShadowElement = function ($element) { + var $parent, inputName, uniqueId, $shadowElement; + + $parent = $element.parent(); + inputName = $element.attr('name'); + $element.removeAttr('name'); + + $shadowElement = $('<input>') + .attr('type', 'hidden') + .attr('name', inputName); + + $element.data('shadow-element', $shadowElement); + + $parent.append($shadowElement); + }; +})(QfqNS); + diff --git a/mockup/api/typeahead.php b/mockup/api/typeahead.php new file mode 100644 index 0000000000000000000000000000000000000000..7927aa3c93860aae26ce33d7335581a507998d40 --- /dev/null +++ b/mockup/api/typeahead.php @@ -0,0 +1,81 @@ +<?php +/** + * @author Rafael Ostertag <rafael.ostertag@math.uzh.ch> + */ + +header('Content-Type: application/json'); + +if (!isset($_GET['query'])) { + echo json_encode(['message' => 'wrong arguments']); + exit(0); +} + +if (!isset($_GET['sip'])) { + echo json_encode(['message' => 'no sip']); + exit(0); +} + +$dict = [ + ['value' => "Alabama", 'key' => "AL"], + ['value' => "Alaska", 'key' => "AK"], + ['value' => "Arizona", 'key' => "AZ"], + ['value' => "Arkansas", 'key' => "AR"], + ['value' => "California", 'key' => "CA"], + ['value' => "Colorado", 'key' => "CO"], + ['value' => "Connecticut", 'key' => "CT"], + ['value' => "Delaware", 'key' => "DE"], + ['value' => "Florida", 'key' => "FL"], + ['value' => "Georgia", 'key' => "GA"], + ['value' => "Hawaii", 'key' => "HI"], + ['value' => "Idaho", 'key' => "ID"], + ['value' => "Illinois", 'key' => "IL"], + ['value' => "Indiana", 'key' => "IN"], + ['value' => "Iowa", 'key' => "IA"], + ['value' => "Kansas", 'key' => "KS"], + ['value' => "Kentucky", 'key' => "KY"], + ['value' => "Louisiana", 'key' => "LA"], + ['value' => "Maine", 'key' => "ME"], + ['value' => "Maryland", 'key' => "MD"], + ['value' => "Massachusetts", 'key' => "MA"], + ['value' => "Michigan", 'key' => "MI"], + ['value' => "Minnesota", 'key' => "MN"], + ['value' => "Mississippi", 'key' => "MS"], + ['value' => "Missouri", 'key' => "MO"], + ['value' => "Montana", 'key' => "MT"], + ['value' => "Nebraska", 'key' => "NE"], + ['value' => "Nevada", 'key' => "NV"], + ['value' => "New Hampshire", 'key' => "NH"], + ['value' => "New Jersey", 'key' => "NJ"], + ['value' => "New Mexico", 'key' => "NM"], + ['value' => "New York", 'key' => "NY"], + ['value' => "North Carolina", 'key' => "NC"], + ['value' => "North Dakota", 'key' => "ND"], + ['value' => "Ohio", 'key' => "OH"], + ['value' => "Oklahoma", 'key' => "OK"], + ['value' => "Oregon", 'key' => "OR"], + ['value' => "Pennsylvania", 'key' => "PA"], + ['value' => "Rhode Island", 'key' => "RI"], + ['value' => "South Carolina", 'key' => "SC"], + ['value' => "South Dakota", 'key' => "SD"], + ['value' => "Tennessee", 'key' => "TN"], + ['value' => "Texas", 'key' => "TX"], + ['value' => "Utah", 'key' => "UT"], + ['value' => "Vermont", 'key' => "VT"], + ['value' => "Virginia", 'key' => "VA"], + ['value' => "Washington", 'key' => "WA"], + ['value' => "West Virginia", 'key' => "WV"], + ['value' => "Wisconsin", 'key' => "WI"], + ['value' => "Wyoming", 'key' => "WY"], +]; + +$result = []; +foreach ($dict as $obj) { + if (stristr($obj['value'], $_GET['query']) || + stristr($obj['key'], $_GET['query']) + ) { + $result[] = $obj; + } +} + +echo json_encode($result); +?> \ No newline at end of file diff --git a/mockup/typeahead.html b/mockup/typeahead.html new file mode 100644 index 0000000000000000000000000000000000000000..b4b2debdc15ee379162f16e8be646a9fb71948cb --- /dev/null +++ b/mockup/typeahead.html @@ -0,0 +1,163 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <link rel="stylesheet" href="../css/bootstrap.min.css"> + <link rel="stylesheet" href="../css/bootstrap-theme.min.css"> + <link rel="stylesheet" href="../css/jqx.base.css"> + <link rel="stylesheet" href="../css/qfq-bs.css"> + <title>Type Ahead</title> +</head> +<body> + + +<label>Submit to + <select name="submitTo" id="submitTo"> + <option>404 error</option> + <option>save_error_matno.json</option> + <option>save_error_geburtstag.json</option> + <option>save_no_redirect.json</option> + <option>save_server_redirect.json</option> + <option>save_client_redirect.json</option> + </select> +</label> + +<label>Delete URL + <select name="deleteUrl" id="deleteUrl"> + <option>404 error</option> + <option>delete_client_redirect.json</option> + <option>delete_error.json</option> + <option>delete_no_redirect.json</option> + <option>delete_server_redirect.json</option> + </select> +</label> + +<label>Upload to + <select name="uploadTo" id="uploadTo"> + <option>404 error</option> + <option>uploadhandler.php</option> + <option>uploadhandler_error.php</option> + </select> +</label> + +<label>File Delete Url + <select name="fileDeleteUrl" id="fileDeleteUrl"> + <option>404 error</option> + <option>delete_file_ok.json</option> + <option>delete_file_error.json</option> + </select> +</label> + + +<div class="container-fluid"> + <div class="row hidden-xs"> + <div class="col-md-12"> + <h1>Title with a long text</h1> + </div> + </div> + + <div class="row"> + <div class="col-md-12 "> + <div class="btn-toolbar pull-right" role="toolbar"> + <div class="btn-group" role="group"> + <button id="save-button" type="button" class="btn btn-default navbar-btn" + data-class-on-change="wdc"><span + class="glyphicon glyphicon-ok"></span></button> + <button id="close-button" type="button" class="btn btn-default navbar-btn"><span + class="glyphicon glyphicon-remove"></span></button> + </div> + <div class="btn-group" role="group"> + <button id="delete-button" type="button" class="btn btn-default navbar-btn"><span + class="glyphicon glyphicon-trash"></span></button> + </div> + <div class="btn-group" role="group"> + <a id="form-new-button" href="personmock.html?s=badcaffe1" class="btn btn-default navbar-btn"><span + class="glyphicon glyphicon-plus"></span></a> + </div> + </div> + </div> + + </div> + + + <form id="myForm" class="form-horizontal" data-toggle="validator"> + + <div id="formgroup1" class="form-group"> + <div class="col-md-2"> + <label for="dropdown1" class="control-label">Text input</label> + </div> + + <div class="col-md-6"> + <input id="dropdown1" type="text" class="form-control qfq-type-ahead" name="dropdown1" + data-sip="abcde" data-template="template1"> + </div> + + </div> + + <div id="formgroup2" class="form-group"> + <div class="col-md-2"> + <label for="dropdown2" class="control-label">Text input 2</label> + </div> + + <div class="col-md-6"> + <input id="dropdown2" type="text" class="form-control qfq-type-ahead" name="dropdown2" + data-sip="abcdef"> + </div> + + </div> + + </form> +</div> + +<script type="text/x-handlebars-template"> + +</script> + +<script src="../js/jquery.min.js"></script> +<script src="../js/bootstrap.min.js"></script> +<script src="../js/validator.min.js"></script> +<script src="../js/jqx-all.js"></script> +<script src="../js/EventEmitter.min.js"></script> +<script src="../js/typeahead.bundle.min.js"></script> +<script src="../js/qfq.debug.js"></script> +<script type="text/javascript"> + $(function () { + var qfqPage = new QfqNS.QfqPage({ + tabsId: 'myTabs', + formId: 'myForm', + submitTo: 'api/' + $("#submitTo").val(), + deleteUrl: 'api/' + $("#deleteUrl").val(), + fileUploadTo: 'api/' + $("#uploadTo").val(), + fileDeleteUrl: 'api/' + $("#fileDeleteUrl").val(), + typeAheadUrl: 'api/typeahead.php' + }); + + $("#submitTo").on("change", function (evt) { + qfqPage.settings.submitTo = 'api/' + $(evt.target).val(); + qfqPage.qfqForm.submitTo = 'api/' + $(evt.target).val(); + }); + + $("#deleteUrl").on("change", function (evt) { + qfqPage.settings.deleteUrl = 'api/' + $(evt.target).val(); + qfqPage.qfqForm.deleteUrl = 'api/' + $(evt.target).val(); + }); + + $("#uploadTo").on("change", function (evt) { + qfqPage.settings.fileUploadTo = 'api/' + $(evt.target).val(); + qfqPage.qfqForm.fileUploader.targetUrl = 'api/' + $(evt.target).val(); + }); + + $("#fileDeleteUrl").on("change", function (evt) { + qfqPage.settings.fileDeleteUrl = 'api/' + $(evt.target).val(); + qfqPage.qfqForm.fileDeleter.targetUrl = 'api/' + $(evt.target).val(); + }); + + $('#myForm').on('invalid', function () { + console.log("Invalid event catched"); + }); + + QfqNS.Log.level = 0; + }); +</script> +</body> +</html>