Skip to content
This repository has been archived by the owner on Jul 9, 2024. It is now read-only.

Commit

Permalink
feat(query): updates query page to match search
Browse files Browse the repository at this point in the history
- links views to search page
- add summary tab
- fixes search button
- fixes gql to properly handle ajax autocomplete
  • Loading branch information
Shane Wilson committed Mar 10, 2015
1 parent 96434f0 commit c5cdab2
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 201 deletions.
278 changes: 164 additions & 114 deletions app/scripts/components/gql/gql.directive.ts
Original file line number Diff line number Diff line change
@@ -1,136 +1,180 @@
module ngApp.components.gql.directives {

import IFilesService = ngApp.files.services.IFilesService
import IGDCWindowService = ngApp.models.IGDCWindowService;
import IFilesService = ngApp.files.services.IFilesService;
import IParticipantsService = ngApp.participants.services.IParticipantsService;
import IFiles = ngApp.files.models.IFiles;
import IParticipants = ngApp.participants.models.IParticipants;

interface IGqlFilters {
op: string;
content: IGqlFilters[]
}

interface IGqlResult {
filters: IGqlFilters;
}

interface IGqlSyntaxError {
column: number;
expected: IGqlExpected[];
found: string;
line: number;
message: string;
name: string;
offset: number;
}

interface IGqlExpected {
description?: string;
type?: string;
value: string;
icon?: string;
active?: boolean;
text: string;
}

interface IGqlScope extends ng.IScope {
onChange(): void;
clearActive(): void;
setActive(active: number);
showResults();
cycle(val: number);
keypress(e: any);
caret(input:any, offset: number);
enter();
gql: IGqlResult;
query: string;
Error: IGqlSyntaxError;
errors: IGqlExpected[];
totalErrors: IGqlExpected[];
ajax: boolean;
term: string;
focus: boolean;
active: number;
}

/* @ngInject */
function gql($window: ng.IWindowService,
$compile: ng.ICompileService,
FilesService: IFilesService,
ParticipantsService: IParticipantsService
): ng.IDirective {
function gqlInput($window: IGDCWindowService,
$compile: ng.ICompileService,
FilesService: IFilesService,
ParticipantsService: IParticipantsService): ng.IDirective {
return {
restrict: 'E',
replace: true,
scope: {
gql: '=',
query : '='
query: '='
},
templateUrl: "components/gql/templates/gql.html",
link: function($scope: ng.IScope, element) {
var ajax = false;
$scope.focus = false;
link: function ($scope: IGqlScope, element: any) {
var inactive = -1;
$scope.active = inactive;

$scope.onChange = () => {
$scope.focus = true;
try {
$scope.gql = $window.gql.parse($scope.query);
$scope.errorMsg = $scope.error = null;
$scope.tErrors = [];
$scope.errors = _.map(_.take(_.filter($scope.errors, (e) => {
return $scope.contains(e.value, last);
}), 10), (e) => {
var xs:string[] = e.value.split(".");
if (xs.length > 1) {
e.icon = xs[0];
xs.shift();
}
e.text = xs.join(".");
return e;
});
//$scope.errors = [];
} catch(Error) {
console.log(ajax);
$scope.ajax = false;
$scope.term = "";
$scope.focus = false;
$scope.active = inactive;

$scope.gql = null;
$scope.errorMsg = Error.message;
$scope.offset = Error.offset;
function contains(phrase: string, sub: string): boolean {
var phraseStr = (phrase + "").toLowerCase();
return phraseStr.indexOf((sub + "").toLowerCase()) > -1;
}

function clean(e: string): boolean {
return (e !== undefined) && ['[A-Za-z0-9\\-_.]', '[0-9]', '[ \\t\\r\\n]', '"', '('].indexOf(e) == -1;
}

function formatForAutoComplete(errors: IGqlExpected[]): IGqlExpected[] {
return _.map(_.take(errors, 10), (e) => {
var es: string[] = e.value.split(".");
if (es.length > 1) {
e.icon = es[0];
es.shift();
}
e.text = es.join(".");
return e;
});
}

// Filter out regex errors
if (!ajax) {
var last = $scope.query.substr($scope.offset).split(" ")[0];
$scope.tErrors = _.filter(Error.expected, (err) => {
var e = err.value;
return (e !== undefined) && ['[A-Za-z0-9\\-_.]', '[0-9]', '[ \\t\\r\\n]', '"', '('].indexOf(e) == -1;
function ajaxRequest(field: string): ng.IPromise {
var xs = field.split(".");
var doc_type = xs.shift();
var facet = xs.join(".");

if (doc_type == "files") {
return FilesService.getFiles({
facets: [facet],
size: 0,
filters: {}
}).then((fs: IFiles): IGqlExpected[] => {
return _.map(fs.aggregations[facet].buckets, (b) => {
return {text: b.key, value: b.key};
});
});
} else {
return ParticipantsService.getParticipants({
facets: [facet],
size: 0,
filters: {}
}).then((fs: IParticipants): IGqlExpected[] => {
return _.map(fs.aggregations[facet].buckets, (b) => {
return {text: b.key, value: b.key};
});
});
}
}

function gqlParse() {
try {
// setup gql
$scope.gql = $window.gql.parse($scope.query);
// if ajax
// T: filter ajax response
if ($scope.ajax) {
var xs: string[] = $scope.query.substr($scope.Error.offset).split(" ");
var term = xs[xs.length - 1];
$scope.errors = formatForAutoComplete(_.filter($scope.totalErrors, (e) => {
return contains(e.value, term);
}));
} else {
var qs = $scope.query.substr($scope.offset).split(" ");
var last = qs[qs.length - 1];
$scope.tErrors = $scope.errors;
// F: reset errors
$scope.totalErrors = $scope.errors = [];
}
} catch (Error) {
// capture Error info
$scope.Error = Error;

// 1 filter errors by current search term
// 2 limit number of results
// 3 format results
console.log(1, last);
$scope.errors = _.map(_.take(_.filter($scope.tErrors, (e) => {
return $scope.contains(e.value, last);
}), 10), (e) => {
var xs: string[] = e.value.split(".");
if (xs.length > 1) {
e.icon = xs[0];
xs.shift();
}
e.text = xs.join(".");
return e;
// determine if ajax needed
$scope.ajax = _.some(Error.expected, (e: IGqlExpected): boolean => {
return '[A-Za-z0-9\\-_.]' == e.value
});

if (!ajax) {
// Split the query based on where the error is
// ex. modifying the middle of a search
var left = $scope.query.substr(0, $scope.offset);
// remove extra spaces
var qs = _.filter(left.split(" "), (x) => {
return x.length;
if ($scope.ajax) {
var qs: string[] = _.filter($scope.query.substr(0, Error.offset).split(" "), (x: string): boolean => {
return !!x.length;
});

// left of current search term
var op = qs[qs.length - 1];
var op: string = qs[qs.length - 1];
// 2 left of current search term
var field = qs[qs.length - 2];

// if left of search is in ops list send ajax
if (['!=', '='].indexOf(op) !== -1) {
ajax = true;
var fs = field.split(".");
var type = fs[0];
fs.shift();
var facet = fs.join(".");

if (type == "files") {
FilesService.getFiles({
facets: [facet],
size: 0
}).then((fs) => {
$scope.errors = _.map(fs.aggregations[facet].buckets, (b) => {
return {text: b.key, value: b.key};
});
});
} else {
ParticipantsService.getParticipants({
facets: [facet],
size: 0
}).then((fs) => {
$scope.errors = _.map(fs.aggregations[facet].buckets, (b) => {
return {text: b.key, value: b.key};
});
});
}
}
var field: string = qs[qs.length - 2];
ajaxRequest(field).then(function (es: IGqlExpected[]) {
$scope.totalErrors = $scope.errors = es;
});
} else {
var xs: string[] = $scope.query.substr(Error.offset).split(" ");
var term = xs[0];
$scope.totalErrors = Error.expected;
$scope.errors = formatForAutoComplete(_.filter($scope.totalErrors, (e) => {
return contains(e.value, term) && clean(e.value);
}));
}
}
};
}

$scope.contains = function (phrase, sub) {
var phraseStr = (phrase + "").toLowerCase();
return phraseStr.indexOf(sub.toLowerCase()) > -1;
$scope.onChange = () => {
$scope.focus = true;
gqlParse();
};

$scope.clearActive = () => {
$scope.clearActive = () => {
if ($scope.errors) {
for (var i = 0; i < $scope.errors.length; ++i) {
$scope.errors[i].active = false;
Expand All @@ -139,13 +183,13 @@ module ngApp.components.gql.directives {
}
};

$scope.setActive = (active) => {
$scope.setActive = (active: number) => {
$scope.clearActive();
$scope.active = active;
$scope.errors[active].active = true;
};

$scope.cycle = (val) => {
$scope.cycle = (val: number) => {
$scope.showResults();

var active = $scope.active + val;
Expand All @@ -164,10 +208,14 @@ module ngApp.components.gql.directives {
return !!($scope.focus && $scope.query.length > 0 && results);
};

$scope.keypress = (e) => {
$scope.keypress = (e: any) => {
if (e.which === 13 || e.keyCode === 13) {
e.preventDefault();
$scope.enter(e);
$scope.enter();
} else if (e.which === 27 || e.keyCode === 27) {
$scope.focus = false;
} else if (e.which === 32 || e.keyCode === 32) {
$scope.ajax = false;
} else if (e.which === 38 || e.keyCode === 38) {
e.preventDefault();
$scope.cycle(-1);
Expand All @@ -177,7 +225,7 @@ module ngApp.components.gql.directives {
}
};

$scope.caret = (input, offset) => {
$scope.caret = (input:any, offset: number) => {
if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(offset, offset);
Expand All @@ -191,16 +239,17 @@ module ngApp.components.gql.directives {
}
};

$scope.enter = (e) => {
var left = $scope.query.substr(0, $scope.offset);
var qs = _.filter($scope.query.substr($scope.offset).split(" "), (x) => {
$scope.enter = (): void => {
$scope.ajax = false;
var left = $scope.query.substr(0, $scope.Error.offset);
var qs = _.filter($scope.query.substr($scope.Error.offset).split(" "), (x) => {
return x.length;
});

var active = $scope.active == -1 ? 0 : $scope.active;

if ($scope.errors.length) {
var oldOffset = $scope.offset;
var oldOffset = $scope.Error.offset;
var v = $scope.errors[active].value;
var v = v == "in" ? "in [" : v;

Expand All @@ -222,6 +271,8 @@ module ngApp.components.gql.directives {
$scope.focus = false;
}

gqlParse();

$scope.$on('application:click', blur);

element.after($compile('<gql-dropdown></gql-dropdown>')($scope));
Expand Down Expand Up @@ -252,7 +303,6 @@ module ngApp.components.gql.directives {
}

angular.module("gql.directives", [])
.directive("gql", gql)
.directive("gql", gqlInput)
.directive("gqlDropdown", gqlDropdown);
}

20 changes: 11 additions & 9 deletions app/scripts/components/gql/templates/gql.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<textarea
class="form-control"
style="border:none;box-shadow: none"
data-ng-model="query"
data-ng-change="onChange()"
data-ng-keydown="keypress($event)"
aria-label="Advanced Query"
placeholder="Start Typing Your Query...."
data-ng-trim="false"
role="textbox">
class="form-control"
data-ng-class="{'alert-danger': totalErrors.length, 'alert-success': gql}"
style="border:none;box-shadow: none"
data-ng-model="query"
data-ng-model-options="{ debounce: 200 }"
data-ng-change="onChange()"
data-ng-keydown="keypress($event)"
aria-label="Advanced Query"
placeholder="Start Typing Your Query...."
data-ng-trim="false"
role="textbox">
</textarea>
Loading

0 comments on commit c5cdab2

Please sign in to comment.