I am seeking to create a reusable "employee lookup" control.
Note:I am assuming that a partial view is the best way to go.
I want multiple buttons on the page
Each button will call a PartialView and each button will have a specific textbox
Each partial will contain multiple Results (items)
On clicking one of the results I want to populate the button's textbox, that made the call, with the result
How am i able to do this, since the page will have multiple buttons and textboxes?
This control needs to be able to be called by multiple buttons
So, those buttons call an action which will render the partial which has those results?
I'm seeing multiple ways to do this. The easiest way is:
<button id="btn1" class="btns" data-target="txt1" type="button">A</button>
<button id="btn2" class="btns" data-target="txt2" type="button">B</button>
<input type="text" id="txt1" />
<input type="text" id="txt2" />
<div id="render">
</div>
<script>
var ajaxActive = false;
$(function() {
$(".btns").on('click', function () { // Bind the onclick of the button, so any button with this class may call the following function
var _button = $(this);
getItems(_button);
});
});
function getItems(_button) {
var bind = function (_button, results) {
$("#render").empty();
$("#render").append(results); // Append the partialview to the current view's div
$("#render .itemResult").on('click', function () { // Bind the onclick of the result
var resultValue = $(this).text(); // Or any other value that come from the result
var targetId = "#" + _button.data('target'); // Id of the input (Target) which comes from the clicked button
$(targetId).val(resultValue); // Change the input target value with the result one
});
};
if (ajaxActive) {
$.get('/Controller/Action') // Get the partialview
.done(function (results) {
bind(_button, results);
});
}
else {
var results = simulateCall(); // Get the results
bind(_button, results);
}
}
function simulateCall() { // Simulate a call to the server
return "<div class='items'> <div class='itemResult'>ABC</div> <div class='itemResult'>DEF</div> </div>";
}
</script>
PS: Here is a working demo
Keep in mind that i placed some sort of "call" to simulate it going to the database
Related
I have a working UI Auto complete with jQuery. I wanted to change the way it worked. Instead of a new browser tab opening with the user selects a value from the list I wanted the user to first pick a value then click a search button to trigger the event.
It works but if you perform a search and then a second search it will trigger the previous URL and new URL at the same time. Also if you perform a search then click the search button without typing anything into the search input it triggers the previous search. Weird right? I'll add my code but I think a codepen example will help clarify what I mean.
The other issue I was having is I am trying to set up a custom alert if the value typed is not in the array but I get the invalid error message no matter what I type. I added that as well in the code. It is one of the if statements.
JS
var mySource = [
{
value: "Google",
url: "http://www.google.com"
},
{
value: "Yahoo",
url: "https://www.yahoo.com"
},
{
value: "Hotmail",
url: "https://hotmail.com"
},
{
value: "Reddit",
url: "https://www.reddit.com"
}
];
//Logic for ui-autocomplete
$(document).ready(function() {
$("input.autocomplete").autocomplete({
minLength: 2,
source: function(req, resp) {
var q = req.term;
var myResponse = [];
$.each(mySource, function(key, item) {
if (item.value.toLowerCase().indexOf(q) === 0) {
myResponse.push(item);
}
if (item.value.toUpperCase().indexOf(q) === 0) {
myResponse.push(item);
}
//Add if statement here to determine if what the user inputs is in the
// array
//and if not in the array give an error to #textAlert.
//Example
if (item.value.indexOf(q) != myResponse) {
$('#alertText').text("Invalid Search");
} else {
return false;
}
});
resp(myResponse);
},
select: function(event, ui) {
$('#appSearchBtn').one("click", function() {
window.open(ui.item.url);
$('#appsearch').val('');
return false;
});
}
});
});
//Input and ui text clears when clicked into
$(document).ready(function() {
var input = document.querySelector('#appsearch');
var ui = document.querySelector(".ui-helper-hidden-accessible");
input.onclick = function() {
input.value = '';
ui.textContent = '';
};
});
HTML
<p id="alertText"></p>
<div class="input-group">
<input type="text" id="appsearch" class="form-control autocomplete" placeholder="Application Search" />
<span class="input-group-btn">
<button class="btn btn-primary inputBtn" id="appSearchBtn" type="button">Search</button>
</span>
</div>
Here is a Code pen https://codepen.io/FrontN_Dev/pen/MEmMRz so you can see how it works. I also added how it should work and what the bugs are.
9/29/17 #0732
I resolved the issue with the event firing the same URL over and over but I still need help with the custom invalid search message that appears for every search even if the value is in the array.
I'm trying to figure this out for a while.
All the examples I saw, use the html with input and span elements manually inserted
I have the following code that generate form and its datepicker elements dynamically:
#using (Html.BeginForm("Reload", "FileDate", FormMethod.Post, new { returnUrl = this.Request.RawUrl, id = "DateForm", onsubmit = "return ValidateDate();" } ))
{
#(Html.Kendo().DatePicker()
.Name("Date")
.Value(Session["FileDate"] == null ? DateTime.Now : Convert.ToDateTime(Session["FileDate"].ToString()))
.Events(e => e
.Change("datepicker_change")
)
)
#Html.Hidden("returnUrl", this.Request.RawUrl)
<script>
function datepicker_change() {
if(ValidateDate()){
$("#DateForm").submit();
}
}
</script>
}
When form is generated, I have the following code on the page:
This is a validation:
<script>
$(document).ready(function() {
$("#mainMenu").kendoMenu();
$("#Date").attr('required', 'required');
$("#Date").attr('data-WrongFormat-msg', 'Date Format is Wrong');
var validator = $("#container").kendoValidator({
rules: {
WrongFormat: function (input) {
if (input.is("[data-role=datepicker]")) {
var dateBox = input.data("kendoDatePicker");
return input.data("kendoDatePicker").value();
} else {
return true;
}
}
}
})
});
function ValidateDate()
{
var validator = $("#container").data("kendoValidator");
if (validator.validate()) {
return true;
}
else
{
return false;
}
}
</script>
When I provide the incorrect input or no input at all, I get the correct message in the span. However, this span section modifies the layout of the page:
How can I fix that, so my error span is placed underneath my form, the way it is shown in some examples like here: http://dojo.telerik.com/ikUfu:
I have the exact same issue. The problems seems to be that the validation message span element is in the wrong place for DatePickers. It is inside this element:
but, in all other widgets, it's inside the spam element one level higher:
So, it seems this is a bug in Telerik at the moment. It works for other widgets, but not for DatePicker. I'll see and find if this bug is already reported, and if not, report it. If you desperately need it fixed asap, I assume you could try some jquery magic to move the span element.
I've faced it too. Is there any proper solution for this issue?
My workaround in a nutshell:
Delete all possible old error messages
Move the new one into the proper HTML container
<div id="div_id">
<input id="input_id" type="text">
</div>
<script>
var validatable = $("[id='input_id'").kendoValidator({
rules: {
minimumLengthRule: function (input) {
var trimmedInputValue = $.trim(input.val());
return trimmedInputValue.length > 0 ;
}
},
messages: {
minimumLengthRule: "The input length is too short."
}
}).data("kendoValidator");
validatable.bind("validateInput", function (e) {
$("#div_id > span").not(':first').remove(); // 1.
if (!e.valid) {
$("[id='input_id_validationMessage'").appendTo('#div_id'); // 2.
}
});
</script>
My viewModel has an array called 'Items'. I want to display the contents of 'Items' using a foreach binding. Everything works fine when I use regular HTML. But does not work with a dialogue box which I created using jQueryUI.
HTML:
<div id="skus0">
<div id="skus1">
<ul data-bind="foreach: Items">
<li data-bind="text:Name"></li>
</ul>
</div>
<input type="button" id="openQryItems" class="btn btn-info" value="Open" data-bind="click:openQueryItems" />
</div>
JavaScript:
// my view model
var viewModel = {
Items: [{Name:'Soap'},{Name:'Toothpaste'}]
};
// JS to configure dialogue
$("#skus1").dialog({
autoOpen: false,
width: 500,
modal: true,
buttons: {
"OK": function () {
$(this).dialog("close");
},
"Cancel": function () {
$(this).dialog("close");
}
}
});
// for mapping my model using ko.mapping plugin
var zub = zub || {};
zub.initModel = function (model) {
zub.cycleCountModel = ko.mapping.fromJS(model);
zub.cycleCountModel.openQueryItems = function () {
$("#skus1").dialog("open");
}
ko.applyBindings(zub.cycleCountModel, $("#skus0")[0]);
}
zub.initModel(viewModel);
I have created a fiddle here my fiddle
$.fn.dialog removes the element from its place in the DOM and places it in a new container; this is how it can create a floating window. The problem with this happening is that it breaks data binding, since the dialog DOM is no-longer nested within the top-level data-bound DOM.
Moving the dialog initialization to after ko.applyBindings will enable dialog to yank stuff out of the DOM after the list is populated. Of course, this means that after that point, future changes will still not be reflected, which may be important if you're wanting the opened dialog to change automatically.
If you are wanting the dialog contents to be fully dynamic, you could create a binding handler; we did this in our project. Here's a rough outline of how we did this:
ko.bindingHandlers.dialog = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingCtx) {
var bindingValues = valueAccessor();
var hasAppliedBindings = false;
var elem = $(element);
var options = {
id: ko.utils.unwrapObservable(bindingValues.id),
title: ko.utils.unwrapObservable(bindingValues.title),
// etc...
onOpen: function () {
if (!hasAppliedBindings) {
hasAppliedBindings = true;
var childCtx = bindingCtx.createChildContext(viewModel);
ko.applyBindingsToDescendants(childCtx, element);
}
}
};
elem.dialog(options);
}
return { controlsDescendantBindings: true };
}
...which we used like this:
<div data-bind="dialog: { title: 'some title', id: 'foo', ... }">
<!-- dialog contents -->
</div>
What return { controlsDescendantBindings: true } does is makes sure that outer bindings do not affect anything using the dialog binding handler. Then we create our own Knockout binding "island" after it is pulled out of the DOM, based on the original view model.
Although in our project we also used hybrid jQuery+Knockout, I would highly recommend you avoid this whenever possible. There were so many hacks we had to employ to sustain this type of application. The very best thing you should do is prefer Knockout binding handlers (and I think it has a "component" concept now which I haven't played with) over DOM manipulations to avoid buggy UI management.
I display user comments. Each comment is one div and each div has <a> tag with class 'commentLikeLink'. I bind jquery click event to 'commentLikeLink' class but If I have 10 comments and click on one like button I get event fired 10 times.
I know that this happen's because I have same class multiple times. But How to stop this?
Here's the code:
...
<div class="commentBox"">
...
#Html.ActionLink(likeText, "LikeComment", "Comment", null, new { id = Model.CommentId, #class = "commentLikeLink" })
...
Event code:
$(function () {
$('.commentLikeLink').click(function (event) {
var commentId = event.target.id;
$.ajax({
url: this.href,
type: 'POST',
data: { commentId: commentId },
context: this,
success: function (result) {
if (result.msg == '1') {
$(this).text('Dislike');
}
else if(result.msg == '2') {
$(this).text('Like');
}
}
});
return false;
});
});
You shouldn't be getting 10 clicks. You can bind the click event to the class, but the context in which the event is fired is the individual element, so if you had some markup that looked like this:
<p>
Liked?
<br />
Liked?
<br />
Liked?
<br />
Liked?
</p>
Then this would work, setting the link text to "Liked!" as each one is clicked:
$(document).on("click", ".clickItem", function (ev) {
$(this).text("Liked!");
});
Have you debugged the code? Are you sure you're getting 10 clicks all at once?
yes binding the click event to the class is a better solution, but it can actually get fired multiple times if you use it on the item it self or the class it self !
so instead of having this : $('.commentLikeLink').click(function (event) {//do things here }
you should do this and it will only fire once :
$(document).on("click", ".commentLikeLink", function (ev) {
//do things here
});
I had this happen before when i accidently included the same .click script function multiple times in the page. Make sure your javascript is only included once
Got a slight problem trying to have jquery UI and knockout js to cohoperate. Basically I want to create an accordion with items being added from knockout through a foreach (or template).
The basic code is as follows:
<div id="accordion">
<div data-bind="foreach: items">
<h3></h3>
<div><a class="linkField" href="#" data-bind="text: link"></a></div>
</div>
</div>
Nothing impressive here... The problem is that if I do something like:
$('#accordion').accordion();
The accordion will be created but the inner div will be the header selector (first child, as default) so the effect is not the wanted one.
Fixing stuff with this:
$('#accordion').accordion({ header: 'h3' });
Seems to work better but actually creates 2 accordions and not one with 2 sections... weird.
I have tried to explore knockout templates and using "afterRender" to re-accordionise the div but to no avail... it seems to re-render only the first link as an accordion and not the second. Probably this is due to my beginner knowldge of jquery UI anyway.
Do you have any idea how to make everything work together?
I would go with custom bindings for such functionality.
Just like RP Niemeyer with an example of jQuery Accordion binding to knockoutjs http://jsfiddle.net/rniemeyer/MfegM/
I had tried to integrate knockout and the JQuery UI accordion and later the Bootstrap collapsible accordion. In both cases it worked, but I found that I had to implement a few workarounds to get everything to display correctly, especially when dynamically adding elements via knockout. The widgets mentioned aren't always aware of what is happening with regards to knockout and things can get messed up (div heights wrongly calculated etc...). Especially with the JQuery accordion it tends to rewrite the html as it sees fit, which can be a real pain.
So, I decided to make my own accordion widget using core JQuery and Knockout. Take a look at this working example: http://jsfiddle.net/matt_friedman/KXgPN/
Of course, using different markup and css this could be customized to whatever you need.
The nice thing is that it is entirely data driven and doesn't make any assumptions about layout beyond whatever css you decide to use. You'll notice that the markup is dead simple. This is just an example. It's meant to be customized.
Markup:
<div data-bind="foreach:groups" id="menu">
<div class="header" data-bind="text:name, accordion: openState, click: toggle"> </div>
<div class="items" data-bind="foreach:items">
<div data-bind="text:name"> </div>
</div>
</div>
Javascript:
ko.bindingHandlers.accordion = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
$(element).next().hide();
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var slideUpTime = 300;
var slideDownTime = 400;
var openState = ko.utils.unwrapObservable(valueAccessor());
var focussed = openState.focussed;
var shouldOpen = openState.shouldOpen;
/*
* This following says that if this group is the one that has
* been clicked upon (gains focus) find the other groups and
* set them to unfocussed and close them.
*/
if (focussed) {
var clickedGroup = viewModel;
$.each(bindingContext.$root.groups(), function (idx, group) {
if (clickedGroup != group) {
group.openState({focussed: false, shouldOpen: false});
}
});
}
var dropDown = $(element).next();
if (focussed && shouldOpen) {
dropDown.slideDown(slideDownTime);
} else if (focussed && !shouldOpen) {
dropDown.slideUp(slideUpTime);
} else if (!focussed && !shouldOpen) {
dropDown.slideUp(slideUpTime);
}
}
};
function ViewModel() {
var self = this;
self.groups = ko.observableArray([]);
function Group(id, name) {
var self = this;
self.id = id;
self.name = name;
self.openState = ko.observable({focussed: false, shouldOpen: false});
self.items = ko.observableArray([]);
self.toggle = function (group, event) {
var shouldOpen = group.openState().shouldOpen;
self.openState({focussed: true, shouldOpen: !shouldOpen});
}
}
function Item(id, name) {
var self = this;
self.id = id;
self.name = name;
}
var g1 = new Group(1, "Group 1");
var g2 = new Group(2, "Group 2");
var g3 = new Group(3, "Group 3");
g1.items.push(new Item(1, "Item 1"));
g1.items.push(new Item(2, "Item 2"));
g2.items.push(new Item(3, "Item 3"));
g2.items.push(new Item(4, "Item 4"));
g2.items.push(new Item(5, "Item 5"));
g3.items.push(new Item(6, "Item 6"));
self.groups.push(g1);
self.groups.push(g2);
self.groups.push(g3);
}
ko.applyBindings(new ViewModel());
Is there any reason why you can't apply the accordion widget to the inner div here? For example:
<div id="accordion" data-bind="foreach: items">
<h3></h3>
<div><a class="linkField" href="#" data-bind="text: link"></a></div>
</div>
I attempted the accepted solution and it worked. Just had to make a little change since i was getting following error
Uncaught Error: cannot call methods on accordion prior to initialization; attempted to call method 'destroy'
just had to add following and it worked
if(typeof $(element).data("ui-accordion") != "undefined"){
$(element).accordion("destroy").accordion(options);
}
for details please see Knockout accordion bindings break
You could try this to template it, similar to this:
<div id="accordion" data-bind="myAccordion: { },template: { name: 'task-template', foreach: ¨Tasks, afterAdd: function(elem){$(elem).trigger('valueChanged');} }"></div>
<script type="text/html" id="task-template">
<div data-bind="attr: {'id': 'Task' + TaskId}, click: $root.SelectedTask" class="group">
<h3><b><span data-bind="text: TaskId"></span>: <input name="TaskName" data-bind="value: TaskName"/></b></h3>
<p>
<label for="Description" >Description:</label><textarea name="Description" data-bind="value: Description"></textarea>
</p>
</div>
</script>
"Tasks()" is a ko.observableArray with populated with task-s, with attributes
"TaskId", "TaskName","Description", "SelectedTask" declared as ko.observable();
"myAccordion" is a
ko.bindingHandlers.myAccordion = {
init: function (element, valueAccessor) {
var options = valueAccessor();
$(element).accordion(options);
$(element).bind("valueChanged", function () {
ko.bindingHandlers.myAccordion.update(element, valueAccessor);
});
...
}
What I did was, since my data was being loaded from AJAX and I was showing a "Loading" spinner, I attached the accordion to ajaxStop like so:
$(document).ajaxStart(function(){$("#cargando").dialog("open");}).ajaxStop(function(){$("#cargando").dialog("close");$("#acordion").accordion({heightStyle: "content"});});
Worked perfectly.