jquery autocomplete in variable length list - javascript

Trying to figure out how to do this, using Sanderson begincollectionitems method, and would like to use autocomplete with a field in each row.
I think I see how to add a row with an autocomplete, just not sure the approach for existing rows rendered with guid.
Each row has an of field that the user can optionally point to a record in another table. Each autocomplete would need to work on the html element idfield_guid.
I'm imagining using jquery to enumerate the elements and add the autocomplete to each one with the target being the unique of field for that row. Another thought is a regex that maybe let you enumerate the fields and add autocomplete for each in a loop where the unique field id is handled automatically.
Does that sound reasonable or can you suggest the right way? Also is there a reasonable limit to how many autocomplete on a page? Thanks for any suggestions!
Edit, here's what I have after the help. data-jsonurl is apparently not being picked up by jquery as it is doing the html request to the url of the main page.
$(document).ready(function () {
var options = {
source: function(request, response) {
$.getJSON($(this).data("jsonurl"), request, function (return_data) {
response(return_data.itemList);
});
},
minLength: 2
};
$('.ac').autocomplete(options);
});
<%= Html.TextBoxFor(
x => x.AssetId,
new {
#class = "ac",
data_jsonurl = Url.Action("AssetSerialSearch", "WoTran", new { q = Model.AssetId })
})
%>
And the emitted html look okay to me:
<input class="ac" data-jsonurl="/WoTran/AssetSerialSearch?q=2657" id="WoTransViewModel_f32dedbb-c75d-4029-a49b-253845df8541__AssetId" name="WoTransViewModel[f32dedbb-c75d-4029-a49b-253845df8541].AssetId" type="text" value="2657" />
The controller is not a factor yet, in firebug I get a request like this:
http://localhost:58182/WoReceipt/Details/undefined?term=266&_=1312892089948
What seems to be happening is that the $(this) is not returning the html element but instead the jquery autocomplete widget object. If I drill into the properties in firebug under the 'element' I eventually do see the data-jsonurl but it is not a property of $(this). Here is console.log($this):

You could use the jQuery UI Autocomplete plugin. Simply apply some know class to all fields that require an autocomplete functionality as well as an additional HTML5 data-url attribute to indicate the foreign key:
<%= Html.TextBoxFor(
x => x.Name,
new {
#class = "ac",
data_url = Url.Action("autocomplete", new { fk = Model.FK })
})
%>
and then attach the plugin:
var options = {
source: function(request, response) {
$.getJSON($(this).data('url'), request, function(return_data) {
response(return_data.suggestions);
});
},
minLength: 2
});
$('.ac').autocomplete(options);
and finally we could have a controller action taking two arguments (term and fk) which will return a JSON array of suggestions for the given term and foreign key.
public ActionResult AutoComplete(string term, string fk)
{
// TODO: based on the search term and the foreign key generate an array of suggestions
var suggestions = new[]
{
new { label = "suggestion 1", value = "suggestion 1" },
new { label = "suggestion 2", value = "suggestion 2" },
new { label = "suggestion 3", value = "suggestion 3" },
};
return Json(suggestions, JsonRequestBehavior.AllowGet);
}
You should also attach the autocomplete plugin for newly added rows.

Related

Symfony Forms - dynamically add/remove fields depending on choices

I'm trying to create a form that maps to an entity of the type "Participant". A participant is in a one-to-one relationship with a 'person'. Adding a participant, I want to first give the option to choose a person already in the database and if the right one doesn't exist, create that person with the participant form.
This works if I do it with two pages/forms. The first one trying to choose an existing person, otherwise open a new page with the different form.
First page:
$form->add('person', AjaxEntityType, [ // EntityType but with select2 ajax
'class' => Person::class,
'remote_route' => 'person_ajax_list'
]);
Second page:
$participant->setPerson(new Person());
$form->add('person', PersonType::class);
// adds PersonType fields to the Participant form
Well, that works, but it's terribly slow and unecessary. What I'd rather want is having BOTH of those shown, where the PersonType form fields (first name, last name, title, company, address, etc.) are automatically populated with the persons data, if one is selected. Otherwise, if no Person is selected and the form is submitted with data entered, a new Person should be created and persisted in the database.
It's sadly not possible to render the 'person' twice, once as a dropdown and once as a PersonType form. So how would I go about achieving what I want, without surreal amounts of JavaScript?
My current solution would be to manually create all the required fields with JavaScript and populate them with the person data I'd get with another Ajax request on a onchange event on the person dropdown, then in the PRE_SUBMIT event of the form, remove the 'person' field and add it again as a PersonType field, check if the entered data corresponds to an existing person or a new one and then act accordingly. There has to be a better solution, right?
Form events have sadly otherwise proven majorly pointless, as it's not possible to attach an event listener to a 'change' event on one of the fields.
Thanks.
Ended up solving it with an unmapped person choice field and javascript to automatically update the data (using ajax).
participant/add.twig:
{% block javascripts %}
<script type="text/javascript">
$(document).ready(function () {
function onTrainerChange() {
let trainerId = $('#participant_person_choose').val();
$.get(Routing.generate('person_data_ajax', { id: trainerId }), function (data) {
$('#participant_person_gender').val(data.gender);
$('#participant_person_title').val(data.title);
$('#participant_person_firstName').val(data.firstName);
$('#participant_person_lastName').val(data.lastName);
$('#participant_person_email').val(data.email);
$('#participant_person_telephone').val(data.telephone);
if (data.company) {
let company = $('#participant_person_company');
company.empty();
company.append(new Option(data.company.text, data.company.id));
company.val(data.company.id);
company.trigger('change');
// manipulate dom directly because of .select('data') bug with select2 >=4.0
}
});
};
let trainer = $('#participant_person_choose');
trainer.change(onTrainerChange);
});
</script>
{% endblock %}
ParticipantController add:
$participant = new Participant($seminar);
$person = $participant->getPerson() ?? new Person();
$participant->setPerson($person);
$form = $this->createParticipantForm($participant)
->add('person_choose', AjaxEntityType::class, [
'mapped' => false,
'class' => Person::class,
'remote_route' => 'person_select_ajax',
'placeholder' => 'form.personCreate',
'label' => 'form.person'
])
->add('person', PersonType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($form->get('reservation')->getData()) {
$participant->setInterested();
}
$personEntered = $form->get('person')->getData();
$personChosen = $form->get('person_choose')->getData();
if ($personChosen) {
$person = $personChosen;
$person->setGender($personEntered->getGender());
$person->setTitle($personEntered->getTitle());
$person->setFirstName($personEntered->getFirstName());
$person->setFirstName($personEntered->getLastName());
$person->setCompany($personEntered->getCompany());
$person->setEmail($personEntered->getEmail());
$person->setTelephone($personEntered->getTelephone());
$participant->setPerson($person);
}
$this->getDoctrine()->getManager()->persist($person);
$this->getDoctrine()->getManager()->persist($participant);
}
PersonController Ajax:
/**
* #Route("/{id}/data", name="person_data_ajax", methods={"GET"}, options={"expose": true})
*/
public function dataAjax(Person $person, PhoneNumberHelper $phonenumberHelper)
{
$arr = [
'id' => $person->id,
'gender' => $person->getGender(),
'title' => $person->getTitle(),
'firstName' => $person->getFirstName(),
'lastName' => $person->getLastName(),
'email' => $person->getEMail(),
'telephone' => $person->getTelephone() ? $phonenumberHelper->format($person->getTelephone(), PhoneNumberFormat::NATIONAL) : null,
'company' => $person->getCompany() ? [
'id' => $person->getCompany()->id,
'text' => $person->getCompany()->__toString()
] : null
];
return new JsonResponse($arr);
}
Hope this can help someone else. Really disappointed with how limited Symfonys Forms are.

Conditionally create dropdownlist with same name

I have a value coming in from the Model to determine whether I am in create mode or edit mode... If I'm in Edit mode, I want to set the value of a dropdownlist to the value last saved. If I am in create mode, I want to default my dropdownlist to a default value....
*.ASCX
<% if(Model.isCreate == true)
{
Html.DropDownList("myOptionListName",
new SelectList(ViewBag.MyOptions, "Id", "Name"),
Resources.Global.DefaultMenuItem,
new { style = "width:200px" });
}
else
{
Html.DropDownList("myOptionListName",
new SelectList(ViewBag.MyOptions, "Id", "Name",
ViewBag.LastSavedOption),
new { style = "width:200px" });
}%>
If I only use one or the other without the if, the one menu works in only one scenario... I need to use "myOptionListName" as the name because I have other javascript that references this. As it stands now, the code above doesn't render any drop down.
Q: How can I get the menu to appear based on the if check but have the same name?
You can have your select list created based on your mode of the form and then assign the appropriate select list to your control.
<% var selectList = new SelectList(ViewBag.MyOptions, "Id", "Name");
if(!Model.isCreate)
{
selectList = new SelectList(ViewBag.MyOptions, "Id", "Name",
ViewBag.LastSavedOption);
}
Html.DropDownList("myOptionListName", selectList ,
new { style = "width:200px" });
%>

jquery autocomplete for dynamically inserted elements with unknown id

We have autocomplete working on static form:
$(function() {
return $('#bom_part_name_autocomplete').autocomplete({
minLength: 1,
source: $('#bom_part_name_autocomplete').data('autocomplete-source'),
select: function(event, ui) {
$(this).val(ui.item.value);
},
});
});
The autocomplete above fills in the part name when user choose one. Now we would like to add autocomplete to dynamically inserted elements. Here are a few things we need to consider:
element id is unknown until after insertion
There may be more than one element inserted on the same form.
the id for dynamically inserted elements always starts 'bom_' and ends with '_part_name_autocomplete'. autocomplete needs to match id with this type of pattern.
The digits in ids are different from element to element. For example, the id for 1st insertion may be 'bom_123456789_part_name_autocomplete',
the id for 2nd insertion could be 'bom_123456987_part_name_autocomplete'
None of online examples we found has unknown element ids. Is it possible to do autocomplete on dynamic elements with unknown ID? If it can be done, an example would be greatly appreciated.
UPDATE: here is the rails code to create element:
def link_to_add_fields(name, f, association)
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
render :partial => association.to_s, :locals => {:f => builder, :i_id => 0}
end
link_to_function(name, "add_fields(this, \"#{association}\", \"#{j fields}\")")
end
Here is the javascript for add_fields():
function add_fields(link, association, content) {
var new_id = new Date().getTime();
var regexp = new RegExp("new_" + association, "g");
$(link).parent().before(content.replace(regexp, new_id));
}
Well, after your elements are inserted you can do:
$('[id^=bom_][id$=_part_name_autocomplete]').each(function(){
$(this).autocomplete({ // ...
})
Its easier to just add it on creation though
Referencing an element by ID is not the only way to select it.
You can add the autocomplete when you create the element:
function create_element(data) {
var field = $("<input/>")
field.data('autocomplete-source', data)
$(field).autocomplete({
minLength: 1,
source: $(field).data('autocomplete-source'),
select: function(event, ui) {
$(this).val(ui.item.value);
},
});
return field
}

Change label display name labels, based on the values from db, on dropdown change

Problem Statement: I want to change the display name of labels(#Html.LabelFor) in Razor view of MVC based on the display names which i get from db.
I have added the dropdown list of languages in the _Layout.cshtml
<li>#Html.Action("Index", "LanguageDropdown", new { languageid = Request["languageId"] })</li>
I have created one partial view for drop down:
#model ALCMS.Web.Models.Master_or_Configuration.LanguageDropdownModel
<script type="text/javascript">
function GetLanguage() {
var languageId = $('#LanguageId').val();
var Url = "#Url.Content("~/MasterConfigGeneral/GetLanguage")";
$.ajax({
url: Url,
dataType: 'json',
data: { LanguageId: languageId },
success: function (data) {
}
});
}
</script>
<div style="display:inline-block">
#Html.DropDownListFor(l => l.LanguageID, new SelectList(Model.Languages, "Value", "Text"), "Select Language", new { id = "LanguageId" ,onchange="GetLanguage()" })
</div>
Partial View Controller:
public ActionResult Index(string languageId)
{
//return View();
var languages = dbEntity.LookupLanguages;
var model = new LanguageDropdownModel
{
LanguageID = languageId,
Languages = languages.ToList().Select(l => new SelectListItem
{
Value = Convert.ToString(l.LanguageID),
Text = l.Name
})
};
return PartialView(model);
}
In Controller Json Result method:
public JsonResult GetLanguage(int languageID)
{
JsonResult jsResult = new JsonResult();
objdbGlobalTenant.ddlLanguage = (from lsr in dbEntity.LocaleStringResources
where lsr.LanguageID == languageID
select new SelectListItem()
{
Text = lsr.ResourceValue,
Value = lsr.ResourceName
}).Distinct().ToList<SelectListItem>();
//ViewBag.Language = objdbGlobalTenant.ddlLanguage;
jsResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return jsResult;
}
Now everything is working fine.I'm able to get the selected langaugeID in Json Result method in Controller based on the change event of Language dropdown. Based on this Language ID i'm getting display names(ResourceValue) which i need to apply for the particular view.
Problems:
1>After getting the display names from db how to change display names
of particular view when language change event triggers.?? For
ex:Currently i'm seeing the Create.CSHTML. Now if i change the
language dropdown it should trigger Json Event in controller and
after getting values it should apply the values on the view which it
got from db.
Note: Dropdown is in Layout.cshtml(like master in .aspx)
2>Drop-down which i placed in Layout.cshtml is getting refreshed
every time new view is loaded which inherits(layout.cshtml).How to
make the controller to retain it's state during postback??
3>How to get the selected drop-down item from the layout in multiple
Controllers,to change the display name in each view based on the langaugeid
of dropdown in layout
How to do this??If i'm doing wrong suggest me some other ways...
Below are the suggestions :
Issue 1 :
You may keep one attribute in each label which identifies them uniquely.
Your HTML should render like following
<!-- For English -->
<label label-unique-name="Name">Name</label>
<label label-unique-name="Surname">Surname</label>
<!-- For French -->
<label label-unique-name="Name">nom</label>
<label label-unique-name="Surname">nom de famille</label>
<!-- For Spanish -->
<label label-unique-name="Name">nombre</label>
<label label-unique-name="Surname">apellido</label>
Here label-unique-name is your attribute, which will remain fixed for each language. Now when you change the language from dropdown you will bring the values like below.
<!-- For English -->
<label-unique-name:"Name",label-value:"Name">;<label-unique-name:"Surname",label-value:"Surname">
<!-- For French -->
<label-unique-name:"Name",label-value:"nom">;<label-unique-name:"Surname",label-value:"nom de famille">
<!-- For English -->
<label-unique-name:"Name",label-value:"nombre">;<label-unique-name:"Surname",label-value:"apellido">
Please note : this is for understanding only, it's not a JSON.
Now using jQuery go through each label and replace the label's value. Hope it'll help you.
Issue 2 :
You can save the selected language's value in session, and generate your dropdown accordingly.
#Html.DropDownListFor(l => l.LanguageID, new SelectList(Model.Languages, "Value", "Text"), !string.isNullorEmpty(HttpContext.Current.Sessions["Language"]) ? HttpContext.Current.Sessions["Language"] : "Select Language", new { id = "LanguageId" ,onchange="GetLanguage()" })

How do I keep the original value selected when transitioning to edit mode?

The select lists are not rendering with the correct option selected. I've tried this a number of different ways including a computed selected observable (this.selected = ko.computed(return parseInt(selected(), 10) == this.id; )) and find in array functions.
In production, the dataArea elements would be populated with server side data. Using the divs with "data-" attributes keeps server side and client side scripting separate (I find this helps the designers).
A record would be displayed in non edit mode first with the option to edit by clicking the edit button. In edit mode, the initial values for the record appear in input controls. You would have the option to say, choose another customer and the having the form load new associated projects. Loading a new customer would reset the project list as expected.
So while loading a new customer would work well, its the transition to editing the current values that is causing an issue. The selected project needs to appear in the drop down list. If a new customer is chosen, the list populates with new options and no defaults are required.
http://jsfiddle.net/mathewvance/ZQLRx/
* original sample (please ignore) http://jsfiddle.net/mathewvance/wAGzh/ *
Thanks.
<p>
issue: When the select options are read, the inital value gets reset to the first object in the options. How do I keep the original value selected when transitioning to edit mode?
</p>
<div>
<h2>Edit Quote '1001'</h2>
<div class="editor-row" data-bind="with: selectedCustomer">
<label>Customer</label>
<div data-bind="visible: !$root.isEditMode()">
<span data-bind="text: CompanyName"></span>
</div>
<div data-bind="visible: $root.isEditMode()">
<input type="radio" name="customerGroup" value="1" data-bind="value: id"> Company Name 1
<input type="radio" name="customerGroup" value="2" data-bind="value: id"> Company Name 2
</div>
</div>
<div class="editor-row">
<label>Project</label>
<div data-bind="visible: !isEditMode()">
<span data-bind="text: selectedProject.Name"></span>
</div>
<div data-bind="visible: isEditMode()">
<select data-bind="options: selectedCustomer().projects, optionsText: 'Name', value: selectedProject"></select>
</div>
</div>
<div>
<button data-bind="click: function() { turnOnEditMode() }">Edit</button>
<button data-bind="click: function() { turnOffEditMode() }">Cancel</button>
</div>
</div>
<hr/>
<div data-bind="text: ko.toJSON($root)"></div>
function ajaxCallGetProjectsByCustomer(customerId) {
var database = '[{"CustomerId": 1, "Name":"Company Name 1", "Projects": [ { "ProjectId": "11", "Name": "project 11" }, { "ProjectId": "12", "Name": "project 12" }, { "ProjectId": "13", "Name": "project 13" }] }, {"CustomerId": 2, "Name": "Company Name 2", "Projects": [ { "ProjectId": "21", "Name": "project 21" }, { "ProjectId": "22", "Name": "project 22" }, { "ProjectId": "23", "Name": "project 23" }] }]';
var json = ko.utils.parseJson(database);
//console.log('parseJson(database) - ' + json);
//ko.utils.arrayForEach(json, function(item) {
// console.log('CustomerId: ' + item.CustomerId);
//});
return ko.utils.arrayFirst(json, function(item){
return item.CustomerId == customerId;
});
}
var Customer = function(id, name, projects) {
var self = this;
this.id = ko.observable(id);
this.CompanyName = ko.observable(name);
this.projects = ko.observableArray(ko.utils.arrayMap(projects, function(item) {
return new Project(item.ProjectId, item.Name);
}));
};
Customer.load = function(id) {
var data = ajaxCallGetProjectsByCustomer(id);
var customer = new Customer(
data.CustomerId,
data.Name,
data.Projects);
};
var Project= function(id, name) {
this.id = id;
this.Name = ko.observable(name);
};
var QuoteViewModel = function () {
var self = this;
$customerData = $('#customerData'); // data from html elements
$projectData = $('#projectData');
// intial values to display from html data
var customer = new Customer (
$customerData .attr('data-id'),
$customerData .attr('data-companyName'),
[{"ProjectId": $projectData .attr('data-id'), "Name": $projectData .attr('data-name')}]
)
this.selectedCustomer = ko.observable(customer);
this.selectedProject = ko.observable($projectData.attr('data-id'));
this.isEditMode = ko.observable(false);
this.selectedCustomer.subscribe(function(){
// todo: load customer projects from database api when editing
});
this.turnOnEditMode = function() {
var customerId = self.selectedCustomer().id();
console.log('customerId: ' + customerId);
Customer.load(customerId);
self.isEditMode(true);
};
this.turnOffEditMode = function() {
self.isEditMode(false);
};
};
var viewModel = new QuoteViewModel();
ko.applyBindings(viewModel);
One the initial value you load
this.dongle = ko.observable($dongleData.attr('data-id'));
This would be the string value "3". Where as the dongle html select element is actually saving/expecting to retrieve the object { "Id": "3", "Name": "dongle 3" }.
Here is a working version that gets the correct initial values and allows editing.
http://jsfiddle.net/madcapnmckay/28FVr/5/
If you need to save the a specific value and not the whole dongle/widget object, you can use the optionsValue attribute to store just the id. Here is it working in the same way.
http://jsfiddle.net/madcapnmckay/VnjyT/4/
EDIT
Ok I have a working version for you. I'll try to summarize everything I changed and why.
http://jsfiddle.net/madcapnmckay/jXr8W/
To get the customer info to work
The Customer name was not stored in the ajaxCallGetProjectsByCustomer json so when you loaded a customer there was no way to determine the new name from the data received. I added a Name property to each customer in the json with name "Company Name 1" etc.
To get the projects collection to work
The problem here was as stated originally with the dongles. You initialize the selectedProject observable with $projectData.attr('data-id') which equates to string value of 13. This is incorrect as the select list is configured in such a way that it actually saves/expects to receive the project object itself. Changing this id assignment to an object assignment made the initial value of project work correctly.
var project = ko.utils.arrayFirst(customer.projects(), function(project){
return project.id == Number($projectData.attr('data-id'));
});
this.selectedProject = ko.observable(project);
FYI there was a minor error in the html, the selectedProject.Name needed to be selectedProject().Name. No big deal.
I'm sure you could have figured out those pretty easily. The next bit is where the real issue is. You reload the Customer every time the edit button is clicked. This seems strange and you may want to reconsider that approach.
However what happens is you load a customer object from the server by id. Assign it to the selectedCustomer observable, this actually works fine. But then because the drop down is bound to selectedCustomer().projects and viewModel.selectedProject it expects that selectedProject is a member of selectedCustomer().projects. In the case of objects the equality operator is assessing whether the references match and in your case they do not because the original selectedProject was destroyed with its associated customer when you overwrote the selectedCustomer value. The fact that the ids are the same is irrelevant.
I have put in place a hack to solve this.
var oldProjectId = viewModel.selectedProject().id;
viewModel.selectedCustomer(customer);
var sameProjectDifferentInstance = ko.utils.arrayFirst(customer.projects(), function(project){
return project.id == oldProjectId;
});
viewModel.selectedProject(sameProjectDifferentInstance || customer.projects()[0]);
This saves the old projectId before assigning the new customer, looks up a project object in the new customer object and assigns it or defaults to the first if not found.
I would recommend rethinking when you load objects and how you handle their lifecycle. If you hold the current objects it memory with a full list of projects included you don't need to reload them to edit, simply edit and then send the update back to the server.
You may find it easier to hold json from the server in js variables instead of html dom elements. e.g.
<script>var projectInitialData = '#Model.ProjectInitialData.toJSON()';</script>
Hope this helps.

Categories

Resources