ユーザーターゲティング:地域、都市、通り

私のプロジェクトでは、地理的基盤を固定して、リソースのユーザーを滞在場所で分けることもあります。 しかし、重要な事柄に常に夢中になっていたため、地域の基盤とその視覚化のためのもう少し便利なインターフェイスを使用してアイデアを実現することはできませんでした。

運命と顧客の意志(または顧客の運命または運命の顧客)によって、そのようなタスクが最終的に生じました-ユーザーをセグメント化し、実際にはその使用のために便利なWebフォームを実装するための地域、都市、および道路のデータベースを作成する必要がありました 幸いなことに、顧客はビジネスをロシアに集中させたため、タスクが大幅に簡素化されました。







ロシア連邦の構成エンティティの既製のデータベースをインターネットで検索しても、特定の結果は得られませんでした-KLADRデータベースが見つかりましたが、あまり関連性がないことが判明しました。 さらに見てみると、 KLADRが死んだ後、 FIASが長生きしました。sergpenzaに感謝します 。今、掘る場所があります!



FIASデータベースは、可能な限り完全で関連性が高く、しかも多すぎることが判明しました。多くの不要なデータベースがあります。 ベースのもう1つのマイナス点は、「フラット」であることです。メインプレートはADDROBJ.dbfであり、エリア、地区、都市、および通りが含まれており、これらはすべて自分自身を指します。 別のマイナス-それはロシア連邦の地域のリストを持っていません。 しかし、それは簡単です。ロシアのGNIVTS Federal Tax Serviceのウェブサイトから簡単に解析できます。



データベースをリレーショナルビューに変換するプロセスには進みません。これは日常的な作業であり、投稿の下部に完成したデータベースへのリンクがあります。



そこに優れた拠点。 Webで視覚化するためのインターフェイスを作成する必要があります。この部分では、さらに詳しく説明します。

インターフェイスには、フロントエンドとバックエンドが含まれます。



  1. フロントエンドはhtml、js、jQueryです
  2. MSからのバックエンドMVC(c#)




フロントエンド



タスク:ユーザーは自分の滞在場所に関するデータを入力できる必要があります。これにより、ユーザーは地域、都市、および通りを順番に入力します。

都市の数(160k以上)と各都市の道路の数を考えると、タスクはより複雑になります-ドロップダウンリストを使用する必要はありません。何らかの種類のクイック検索とフィルタリングメカニズムを提供する必要があります。 もちろん、このメカニズムは普遍的であり、地域だけでなく、通りのある都市もカバーする必要があります。

このようなメカニズムは、サイトの適切な場所に接続されたライブラリの形式で実装するのが最適です。 jquery.locateme.jsライブラリに名前を付けましょう。 ライブラリの名前から、jQueryに依存していることは明らかです。 最初は、フレームワークのイデオロギーに従ってjQueryのプラグインを作成するというアイデアがありましたが、最終的には放棄しました。



ライブラリには次の機能が必要です。



実装(スケルトン)
var locateMe = function (wrapperName, fieldName, fieldLabel, url, urlData, applyHandler, cancelHandler) { var _this = this, _urlData = urlData; this.isApplied = false; this.SearchInputLabel = $("<span>").addClass("label").attr("id", fieldName + "_label").html(fieldLabel); this.SearchInput = $("<input/>").addClass("input_search").attr("id", fieldName).attr("type", "text"); this.SearchInputTip = $("<input/>").addClass("input_search_tip").attr("id", fieldName + "_tip").attr("type", "text"); this.SearchResultsTipId = $("<input/>").attr("id", fieldName + "_tip_id").attr("type", "hidden"); this.SearchResults = $("<div>").addClass("results").attr("id", fieldName + "_results"); this.SearchUrl = url; return this; };
      
      







パブリック関数
 this.Reload = function (reloadValues) { if (reloadValues) { _this.SearchInput.val(""); _this.SearchInputTip.val(""); _this.SearchResultsTipId.val(""); _this.SearchResults.hide().empty(); } _methods.setResultsPosition(); }; this.Dispose = function () { this.isApplied = false; this.SearchInputLabel.remove(); this.SearchInput.unbind().remove(); this.SearchInputTip.remove(); this.SearchResultsTipId.remove(); this.SearchResults.unbind().remove(); _this = null; } this.Disable = function (setDisabled) { if (setDisabled) { this.SearchInput.val("").attr("disabled", "disabled"); this.SearchInputTip.val("").attr("disabled", "disabled"); this.SearchResultsTipId.val(""); this.SearchResults.empty().hide(); } else { this.SearchInput.removeAttr("disabled"); this.SearchInputTip.removeAttr("disabled"); } return this; }; this.AjaxRequestParameters = function (data) { _urlData = data; return _urlData; }; this.DefaultValue = function (id, val) { this.SearchResultsTipId.val(id); this.SearchInput.val(val); return this; }; this.Value = function () { return { k: _this.SearchResultsTipId.val(), v: _this.SearchInput.val() }; };
      
      







制御コンストラクターと内部関数
 var _methods = { setResultsPosition: function () { var inputOffset = _this.SearchInput.offset(), inputSize = _methods.objectWH(_this.SearchInput); _this.SearchResults .css("left", inputOffset.left) .css("top", inputOffset.top + inputSize.height - 2) .css("width", inputSize.width - 2); }, retrieveResults: function (query) { if (query && query.length > 0) { var _data = {}; if (_urlData && typeof (_urlData) === "object") { _data = _urlData, _data.searchquery = query; } else _data = { searchquery: query }; $.ajax({ async: true, url: _this.SearchUrl, type: "POST", data: _data, success: function (response) { _methods.fillResults(response); } }); } }, fillResults: function (arr) { _this.SearchResults.empty().hide(); _this.SearchInputTip.val(""); if (arr && arr.length > 1) { $(arr).each(function (i, o) { _this.SearchResults.append("<div class=\"row\" id=\"" + ok + "\">" + ov + "</div>"); }); _this.SearchResults .find("div") .unbind() .click(function () { $(this).addClass("selected"); _methods.resultsApply(); }).end() .css("height", arr.length * 19).show(); } else if (arr && arr.length == 1) { var searchInputValue = _this.SearchInput.val().length, arrayValue = arr[0].v, arrayKey = arr[0].k, tip = _this.SearchInput.val() + arrayValue.substring(searchInputValue, arrayValue.length); _this.SearchResultsTipId.val(arrayKey); _this.SearchInputTip.val(tip); } }, resultsMove: function (direction) { var currentPosition = -1, resultsCount = _this.SearchResults.find(".row").length - 1; $(_this.SearchResults.children()).each(function (i, o) { if ($(o).hasClass("selected")) { currentPosition = i; return; } }); if (direction == "up") { if (currentPosition > 0) { currentPosition--; _this.SearchResults .find("div.selected").removeClass("selected").end() .find("div:eq(" + currentPosition + ")").addClass("selected"); } } else { if (currentPosition < resultsCount) { currentPosition++; _this.SearchResults .find("div.selected").removeClass("selected").end() .find("div:eq(" + currentPosition + ")").addClass("selected"); } } }, resultsApply: function () { var selectedId = 0; if (_this.SearchResultsTipId.val() != "" || _this.SearchResults.find("div").length > 0) { if (_this.SearchResults.is(":visible")) { selectedId = _this.SearchResults.find(".selected").attr("id"); _this.SearchInput.val(_this.SearchResults.find(".selected").html()); _this.SearchInputTip.val(""); _this.SearchResultsTipId.val(selectedId); _this.SearchResults.empty().hide(); } else { selectedId = _this.SearchResultsTipId.val(); _this.SearchInput.val(_this.SearchInputTip.val()); _this.SearchInputTip.val(""); } if (!_this.isApplied) { if (applyHandler && typeof (applyHandler) === "function") { applyHandler(selectedId); } _this.isApplied = true; } } return selectedId; }, objectWH: function (obj) { var r = { width: 0, height: 0 }; r.height += obj.css("height").replace("px", "") * 1; r.height += obj.css("padding-top").replace("px", "") * 1; r.height += obj.css("padding-bottom").replace("px", "") * 1; r.height += obj.css("margin-top").replace("px", "") * 1; r.height += obj.css("margin-bottom").replace("px", "") * 1; r.height += obj.css("border-top-width").replace("px", "") * 1; r.height += obj.css("border-bottom-width").replace("px", "") * 1; r.width += obj.css("width").replace("px", "") * 1; r.width += obj.css("padding-left").replace("px", "") * 1; r.width += obj.css("padding-right").replace("px", "") * 1; r.width += obj.css("margin-left").replace("px", "") * 1; r.width += obj.css("margin-right").replace("px", "") * 1; r.width += obj.css("border-left-width").replace("px", "") * 1; r.width += obj.css("border-right-width").replace("px", "") * 1; return r; } }; var target = $("." + wrapperName); if (target.length > 0) { target .append(this.SearchInputLabel) .append(this.SearchInput) .append(this.SearchInputTip) .append(this.SearchResultsTipId) .append(this.SearchResults); $(window) .resize(function () { _methods.setResultsPosition(); }) .trigger("resize"); this.SearchInput .keydown(function (e) { var val = _this.SearchInput.val(), valLength = val.length; if (e.which > 32 && e.which != 40 && e.which != 38 && e.which != 9 && e.which != 39 && e.which != 46 && e.which != 13) { return true; } else if (e.which == 8 || // [Backspace] e.which == 46) { // [DELETE] if ((valLength - 1) > 0) { _methods.retrieveResults(val.substring(0, valLength - 1)); } if (_this.isApplied) { _this.isApplied = false; _this.SearchResultsTipId.val(""); if (cancelHandler && typeof (cancelHandler) === "function") { cancelHandler(); } } } else if (e.which == 40) { //▼ _methods.resultsMove("down"); } else if (e.which == 38) { //▲ _methods.resultsMove("up"); } else if (e.which == 39) { //→ _methods.resultsApply(); } else if (e.which == 9) { //TAB _methods.resultsApply(); return false; } else if (e.which == 13) { //ENTER _methods.resultsApply(); } }) .keypress(function (e) { var text = _this.SearchInput.val(), pressedChar = String.fromCharCode(e.which || e.keyCode), query = text + pressedChar; _methods.retrieveResults(query); }); }
      
      









次のようにページ上のコントロールを使用します

 var region = new locateMe("field_wrapper", "field_name", "field_label", "search_URL", search_URL_DATA, applyHandler, cancelHandler);
      
      





field_wrapper-クラスセレクター、コントロールが作成されるシェル

field_name-制御フィールドの名前

field_label-検索でフィールドの上に書き込まれるもの

search_URL-検索のために要求されるURL(POSTメソッド)

[search_URL_DATA]-search_URL(オブジェクト)に渡されるオプションのパラメーター

[applyHandler]-関数。フィールドでの検索が完了した後に呼び出されます

[cancelHandler]-フィールドが変更されたときに呼び出される関数(もちろん、検索が完了した場合を除く)



例:

 var region = new locateMe("uloc_region", "region", "", "/region", null, function(selectedId){ alert(" ID:" + selectedId); });
      
      





この例では、div「.uloc_region」に「region」という名前のフィールドが作成されます。 検索するには、パラメーターなしのURL「/地域」が要求され、目的の地域が見つかると、「地域ID:%地域ID%」というテキストのアラートが表示されます。



バックエンド



目的:データベースオブジェクト(地域、都市、または通り)に対するユーザーの検索クエリを満たすフィールドのデータベースからの選択を実装する

自分の都市を検索するユーザーの一般的な動作は、テキストボックスに名前を入力し始めることです。この時点で、フロントエンドコントロールが機能し始め、検索クエリの入力時に自動補完が提供されます。



ソリューションのアーキテクチャ(ソリューション、.sln)は4つのライブラリで構成されています。







BO、DC、DPは一般的なもの(linq、DTO、データベースコンテキスト)であるため、説明する意味はありません。 投稿の最後にソリューション全体を含むアーカイブへのリンク。

UIに関しては、一般的な用語で考えることができます。 つまり、検索方法の署名



  [HttpPost] public JsonResult Region(string searchquery) { return Json(from i in Database.SearchRegions(searchquery, 5) select new { k = i.Id, v = i.Region }); } [HttpPost] public JsonResult City(int regionId, string searchquery) { return Json(from i in Database.SearchCities(regionId, searchquery, 5) select new { k = i.Id, v = i.City }); } [HttpPost] public JsonResult Street(long cityId, string searchquery) { return Json(from i in Database.SearchStreets(cityId, searchquery, 5) select new { k = i.Id, v = i.Street }); }
      
      







簡単です:3つのデータベースオブジェクト用の3つのメソッド。 それぞれがユーザーの検索クエリであるsearchquery文字列を取ります。 最後の2つのメソッドには、もう1つのパラメーターRegionIdとCityIdがあります。これらは、検索する地域(または都市)を示します。 検索結果は5エントリに制限されています。 JSONでシリアル化された匿名型が返されるオブジェクトとして使用されます。vは地域/都市の名前です

または通り、およびkはそれらの識別子です。



デモはこちら



プロジェクト(完全)はこちら (github)

同じ場所にあるベース(ダンプ)



リンク、説明、指示
データベースサーバーとして-MS SQL 2012

データベース2014年の関連性、私は四半期。 クリミアとセヴァストポリの新しい地域とバイコヌールの領土が存在します

バックエンド.Net 4.0、MVC 3



ベースファイル(mdf、log) tyk (githubにコミットされていません。おそらくサイズが大きいです)





UPD
おかげでWindDropアンドリア

修正された\追加:

1.手動入力またはオートコンプリート後に[ENTER]、[TAB]または[右矢印]を繰り返し押します

2.大文字と小文字を区別する自動補完(大文字と小文字を区別する)

3.空のフィールドを含む検索結果を含むウィンドウを表示する

4.地域フィールドへの任意の入力(「Republic of Bashko ...」または「Bashko ....」)は同じ結果になります(データベースを変更せずに)



未修正:

FFのライブラリの動作(キーストローク)が不明確






All Articles