I'm making a data visualisation tool where you can input your own data. The data values are stored in an unordered list like this: <ul><li data-name='name'><a href='#' onclick='showEditDv(this);'>Edit</a><span class='name'>name</span><span class='seperator'> | </span><span class='value'>7</span></li></ul. There can be more than one list item in the list. When you click on the Edit button it calls the showEditDv() function, giving a reference to itself. Before I show the function, I will say that the data object is organised like this:
data ->
name: "root",
children: [ {name: "something", size: "7"},
{name: "something-else", size: "999"} ]
This is the code for the function:
function showEditDv(object) {
var name = $(object).parent().attr("data-name"),
input = new Opentip($(object), {removeElementsOnHide: true, target: null, showOn: null, hideTrigger: "closeButton"}),
disabled = (data.children[getChildIndexByName(name)].hasOwnProperty("children")) ? "disabled" : "";
input.setContent("<label>Name:</label><input type='text' data-prevname='" + name + "' value='" + name + "' class='dv-add-name' /><label>Value:</label><input " + disabled + " type='text' class='dv-add-value' value='" + data.children[getChildIndexByName(name)].size + "' /><button class='callEditDv'>Apply</button>"); // Set content of opentip
input.show();
$("body").on("click", ".callEditDv", function() {
var newname = $(this).siblings(".dv-add-name").val(),
prevname = $(this).siblings(".dv-add-name").attr("data-prevname"),
value = $(this).siblings(".dv-add-value").val();
if (newname !== prevname)
{
data.children[ getChildIndexByName(prevname) ].name = newname; // Update name
$(object).parent().attr("data-name", newname); // Update parent data
$(object).siblings(".name").text(newname); // Update form
}
if (data.children[ getChildIndexByName(newname) ].size !== value)
{
data.children[ getChildIndexByName(newname) ].size = value;
$(object).siblings(".value").text(value);
}
input.hide();
});
}
It uses Opentip, which is just a way of creating dynamic popups / tooltips. The problem is that once you have changed a data value once, when you try to change it again it loops through the code twice! The first time everything works as expected, but the second time it does it again, using the same prevname, which means that getChildIndexByName returns undefined and it can't set the variable causing an error. getChildIndexByName loops through the values of data.children checking the names until it finds a match, and then returns the index of the object in the array.
Thanks in advance!
Try this:
$("body").off('click').on("click",...
jQuery Documentations
Event handlers attached with .bind() can be removed with .unbind().
(As of jQuery 1.7, the .on() and .off() methods are preferred to
attach and remove event handlers on elements.)
change:
$("body").on("click",...
to
$("body").unbind('click').on("click",
Hope this help!
Related
I have a jQuery change function that populates a dropdown list of Titles from the user selection of a Site dropdown list
$("#SiteID").on("change", function() {
var titleUrl = '#Url.Content("~/")' + "Form/GetTitles";
var ddlsource = "#SiteID";
$.getJSON(titleUrl, { SiteID: $(ddlsource).val() }, function(data) {
var items = "";
$("#TitleID").empty();
$.each(data, function(i, title) {
items +=
"<option value='" + title.value + "'>" + title.text + "</option>";
});
$("#TitleID").html(items);
});
});
The controller returns JSON object that populates another dropdown list.
public JsonResult GetTitles(int siteId)
{
IEnumerable<Title> titleList;
titleList = repository.Titles
.Where(o => o.SiteID == siteId)
.OrderBy(o => o.Name);
return Json(new SelectList(titleList, "TitleID", "Name"));
}
The markup is:
<select id="SiteID" asp-for="SiteID" asp-items="#Model.SiteList" value="#Model.Site.SiteID" class="form-control"></select>
<select id="TitleID"></select>
The problem is that the controller method is only touched on the FIRST time a selection is made. For example,
The first time SITE 1 is selected, the controller method will return the updated list of Titles corresponding to SITE 1
If SITE 2 is selected from the dropdown, the controller will return the updated list of Titles corresponding to SITE 2
The user adds/deletes Titles in the database corresponding to SITE 1
User returns to the form and selects SITE 1 from the dropdown. The list still shows the results from step 1 above, not the updates from step 3
If I stop debugging and restart, the selection will now show the updates from step 3.
Similar behavior described in jQuery .change() only fires on the first change but I'm hoping for a better solution than to stop using jQuery id's
The JSON response is:
[{"disabled":false,"group":null,"selected":false,"text":"Title2","value":"2"},{"disabled":false,"group":null,"selected":false,"text":"Title3","value":"1002"},{"disabled":false,"group":null,"selected":false,"text":"Title4","value":"2004"},{"disabled":false,"group":null,"selected":false,"text":"Title5","value":"3"},{"disabled":false,"group":null,"selected":false,"text":"Title6","value":"9004"}]
The issue was that the JSON result was being read from cache as #KevinB pointed out. This was fixed by adding the following line within the change function
$.ajaxSetup({ cache: false });
I would like to know whether is there a way to program populate my kendo checkbox list like the image below through javascript object/array because most of the online search result are creating a list at html.
sample of output
If you already have the checkbox list rendered, then use MVVM checked binding:
http://demos.telerik.com/kendo-ui/mvvm/elements
http://docs.telerik.com/kendo-ui/framework/mvvm/bindings/checked
If you want to render the checkbox list with JavaScript, according to some data, and check the checkboxes at the same time, then use Kendo UI templates and kendo.render()
http://docs.telerik.com/kendo-ui/framework/templates/overview
http://docs.telerik.com/kendo-ui/api/javascript/kendo#methods-render
<ul id="checkboxList"></ul>
<script>
var template = kendo.template("<li>" +
"<label>" +
"<input type='checkbox' #: isChecked ? \" checked='checked'\" : \"\" # />" +
"#: name #" +
"</label>" +
"</li>");
var data = [
{ id: 1, name: "foo", isChecked: true },
{ id: 2, name: "bar", isChecked: false }
];
var html = kendo.render(template, data);
$("#checkboxList").html(html);
</script>
Instead of kendo.render(), you can alternatively use a Kendo UI ListView and an item template. The template definition itself can be the same.
http://demos.telerik.com/kendo-ui/listview/index
http://docs.telerik.com/kendo-ui/api/javascript/ui/listview#configuration-template
I have an Editor Template which contains a table row with (among other stuff) a dropdown/combobox to select a currency. This edit template is shown many times on the same View and it's possible for a user to add these rows as many times as he wants.
I want changes on a row's dropdown to reflect in an EditorFor (the currency's rate) on the same row, so I've added a onchange html parameter:
<td>
#*#Html.LabelFor(model => model.Currency)*#
#Html.DropDownListFor(model => model.Currency, new SelectList(Model.CurrencyList, "Code", "Code"), new { onchange = "updateCurrency(this)" })
#Html.ValidationMessageFor(model => model.Currency)
</td>
My javascript function makes an ajax call to retrieve the rate for the selected currency:
function updateCurrency(elem) {
alert("Currency changed!")
$.ajax({
type: "GET",
url: "Currency?code=" + elem.value,
success: function (msg) {
// The Rate field's Id:
var RateId = "#Html.ClientIdFor(model=>model.Rate)" // // Halp, problem is here!
document.getElementById(RateId).value = msg;
}
});
}
My problem is that
var RateId = "#Html.ClientIdFor(model=>model.Rate)"
has that Html helper which is server-side code. So when i view the page's source code, the javascript code is replicated (once for each row) and all the var RateId = "#Html.ClientIdFor(model=>model.Rate)" are pointing to the most recently added column's EditorFor.
Probably my way of attempting to solve the problem is wrong, but how can I get my javascript code to update the desired field (i.e. the field in the same row as the changed dropdown list).
I believe that one of the problems is that I have the javasript on the Editor Template, but how could I access stuff like document.getElementById(RateId).value = msg; if I did it like that?
Thanks in advance :)
Figured it out. Hoping it helps somebody:
In my view:
#Html.DropDownListFor(model => model.Currency, new SelectList(Model.CurrencyList, "Code", "Code"), new { #onchange = "updateCurrency(this, " + #Html.IdFor(m => m.Rate) + ", " + #Html.IdFor(m => m.Amount) + ", " + #Html.IdFor(m => m.Total) + ")" })
In a separate JavaScript file:
function updateCurrency(elem, RateId, AmountId, TotalId) {
var cell = elem.parentNode // to get the <td> where the dropdown was
var index = rowindex(cell) // get the row number
// Request the currency's rate:
$.ajax({
blah blah blah .........
(RateId[index - 1]).value = 'retreived value'; // Set the rate field value.
});
}
Seems to be working so far.
I'm trying to get my head around observables in knockout js!
However, I'm facing a problem implementing the remove function on an observable array.
My js is as follow:
$(function () {
var data = [{ name: "name1" }, { name: "name2" }, { name: "name3"}];
var viewModel = {
namesList: ko.observableArray(data),
nameToAdd: ko.observable("name4"),
myCustomAddItem: function () {
this.namesList.push({ name: this.nameToAdd() });
},
myCustomRemove: function () {
console.log("before + " + this.nameToAdd());
this.namesList.remove(this.nameToAdd());
console.log("after + " + this.nameToAdd());
}
};
ko.applyBindings(viewModel);
});
and my html is:
Name To add/remove <input type="text" data-bind="value: nameToAdd, valueUpdate: 'afterkeydown'"/>
<ul data-bind="template: {name: 'listTemp1', foreach :namesList}">
</ul>
<p>
<button data-bind="click: myCustomAddItem">Add Item</button>
<button data-bind="click: myCustomRemove">Remove Item</button>
<script id="listTemp1" type="text/html">
<li data-bind="text:name"> </li>
</script>
</p>
my myCustomAddItem works fine, but not the myCustomRemove. I also have put a console.log before and after the this.namesList.remove(this.nameToAdd()); to see if anything's wrong there, but I cannot see any error in there. When I click the "Remove Item" button, firebug console shows the logs but the item's not removed from the list.
Any help appreciated
The parameter to remove should be a function which returns true or false on whether to remove something.
It works quite similarly to the filter function.
In your case, something like this should work:
myCustomRemove: function () {
console.log("before + " + this.nameToAdd());
var nameToAdd = this.nameToAdd();
this.namesList.remove(function(item) {
//'item' will be one of the items in the array,
//thus we compare the name property of it to the value we want to remove
return item.name == nameToAdd;
});
console.log("after + " + this.nameToAdd());
}
[this should be a comment on Jani answer, but I can't still comment on others post, sorry]
Just a small clarification: technically you can call remove() passing the element to remove, see section "remove and removeAll" on http://knockoutjs.com/documentation/observableArrays.html.
the problem with your code is that the elements in 'data' array are objects (containing a property called 'name'), and you are asking to remove from the array the string "name4" (or whatever 'nameToAdd' contains).
You can be tempted to create a new object to pass to remove, like this:
// old code
//this.namesList.remove(this.nameToAdd());
this.namesList.remove({ name: this.nameToAdd() });
but this still fails, because the way javascript object equality works (see, for example: How to determine equality for two JavaScript objects?).
So, in the end you need to use the function anyway.
In this simple example, you can also convert the 'namesList' array to a simple array of strings, and bind "$data" in the template. see http://jsfiddle.net/saurus/usKwA/.
In a more complex scenario, maybe you can't avoid using objects.
[observableArray].remove(function(item) { return item.[whatever] == [somevalue] ; } );
I'm using Jquery mobile, so ignore some of the following overdone css, its not related to the core issue.
I'm having a problem looping over the "Places" in my JSON packet/javascript object. I am getting a response with multiple "Places", but can't seem to figure out how to iterate over them. My 'i' variable in my each loop is working correctly for the first element, and displaying its corresponding name & image.
Here's my server side Django view (pretty straight-forward if your unfamiliar with Python):
def tonight_mobile(request):
callback = request.GET.get('callback', '')
def with_rank(rank, place):
return (rank > 0)
place_data = dict(
Places = [make_mobile_place_dict(request, p) for p in Place.objects.all()]
)
xml_bytes = json.dumps(place_data)
return HttpResponse(xml_bytes, content_type='application/json; charset=utf-8')
My server is acknowledging the request and returning the following:
"GET /api/0.1/tonight-mobile.json&callback=jsonp1293142434434 HTTP/1.1" 200 167
Here's my response:
callback({"Places": [{"url": "http://localhost:8000/api/0.1/places/3.plist",
"image_url": "http://localhost:8000/static/place_logos/Bengals_1.png",
"name": "Bob's Place", "events": 2},
{"url": "http://localhost:8000/api/0.1/places/2.plist",
"image_url": "http://localhost:8000/static/place_logos/Makonde_Mask.gif",
"name": "My Bar", "events": 0},
{"url": "http://localhost:8000/api/0.1/places/1.plist",
"image_url": "http://localhost:8000/static/place_logos/Quintons_1.png",
"name": "Quinton's", "events": 1}]})
------------
My getJSON and callback method have evolved into this:
<script>
function loadJSON(){
$.getJSON("http://localhost:8000/api/0.1/tonight-mobile.json&callback=?", callback(data));
}
function callback(data){
$("#tonight-list").each(data.Places, function(i) {
$(this).append("<li role='option' tabindex='" + data.Places[i] + "' class='ui-li-has-thumb ui-li ui-btn ui-btn-icon-right ui-corner-top ui-corner-bottom ui-controlgroup-last ui-btn-down-c ui-btn-up-c' data-theme='c'><div class='ui-btn-inner ui-corner-top ui-corner-bottom ui-controlgroup-last'><span class='ui-icon ui-icon-arrow-r'></span><div class='ui-btn-text'><img src=" + data.Places[i].image_url + " alt=" + data.Places[i].name + " class='ui-li-thumb ui-corner-tl ui-corner-bl'/><h1 class='list-title ui-li-heading' id='list-title'><a href='detail.html?id=slide' data-transition='slide' class='ui-link-inherit'>" + data.Places[i].name + "</a></h1><span class='ui-li-count ui-btn-up-c ui-btn-corner-all'>" + data.Places[i].events + " events</span></div></div></li>");
});
}
</script>
Am I confusing how the each function iterates over the JSON (which become) Javascript objects? I am pretty sure the each is my issue here, as I am only getting the FIRST element of the "Places" list. Can someone please help me out as to how to structure the looping?
each() doesn't work that way. It takes a set of elements on the page, and loops over them, calling a function for each one. In this case, you are looping over $("#tonight-list"), which will only have one item in it (IDs are supposed to uniquely identify one element in a document).
What you seem to want to do is to loop over the data (not the #tonight-list element), and add the data to that element, right? In that case, you want normal JavaScript to do the loop, something like this:
var list = $("#tonight-list");
for (var i=0, place; place=data.Places[i]; ++i) {
list.append("<li role='option' tabindex='" + i + "'>" + place.name + "</li> ...etc);
}
Note that I have no idea if you are loading trusted data that is meant to contain HTML, untrusted plain text, etc. You may need to encode the data before inserting it or (better) insert it structurally instead of concatenating strings, depending on your use-case.
You are using getJSON and each the wrong way:
function loadJSON(){
$.getJSON("http://localhost:8000/api/0.1/tonight-mobile.json&callback=?", callback);
}
function callback(data){
var target = $("#tonight-list");
$.each(data.Places, function(i) {
target.append("...");
});
}
You have to pass a reference of the callback function do getJSON, not call the function.
If you do $("#tonight-list").each() then you are iterating over the elements selected by the selector. This function takes only one argument (the callback) anyway. See the documentation.
You want $.each() which takes an array as first and a callback as second argument.