What is the certified way to determine the index of the currently selected item in a ComboBox in ExtJS?
Is there a difference on how to do this between ExtJS 3.x and 4?
var combo = new Ext.form.ComboBox(config);
var selectedIndex = combo.selectedIndex; // TODO: Implement
if(selectedIndex > 2) {
// Do something
}
Bonus-points for how to add it as a property to the ComboBox-object.
I think you'll have to use the combo's store for that. Combos have a private findRecord method that'll do a simple search over the store by property and value. You can see an example in the sourcecode itself (Combo.js line 1119).
1) Based on this you could find the selected index this way :
var v = combobox.getValue();
var record = combobox.findRecord(combobox.valueField || combobox.displayField, v);
var index = combobox.store.indexOf(record);
2) Or you could bind yourself to the "select" event which is fired with the combo, the record selected and its index as a parameter.
3) You could also access the view's getSelectedIndexes() but I doubt it's a good solution (in that I'm not sure it's available all the time)
Finally if you want to extend the combobox object I think this should work (if you go with the first solution) :
Ext.override(Ext.form.ComboBox({
getSelectedIndex: function() {
var v = this.getValue();
var r = this.findRecord(this.valueField || this.displayField, v);
return(this.store.indexOf(r));
}
});
In Ext 4.0.2 the same code would look like:
Ext.override(Ext.form.ComboBox, {
getSelectedIndex: function() {
var v = this.getValue();
var r = this.findRecord(this.valueField || this.displayField, v);
return(this.store.indexOf(r));
}
});
Jad, you're missing a closing parenthesis on your return statement... just thought you should know.
If you have a combo where valueField is the id used by the combo's store, you can simply avoid the search:
var v = combobox.getValue();
var record = combobox.findRecord(combobox.valueField || combobox.displayField, v);
var index = combobox.store.indexOf(record);
using this:
var id = combobox.getValue();
var record = store_combobox.getById(id);
Related
I'm trying to set objects into localStorage with a format similar to the following:
[{"1":{"property1":false,"property2":false}},{"2":{"property1":false,"property2":false}}]
Where I'd be able to set the 1 or 2 based on a dynamic value I'm getting from a REST call. What I have so far is:
// check if session exists and create if not
var StorageObject = JSON.parse(localStorage.getItem("session")) || [];
//see if the current id from the REST call is in storage and push with properties if not
if ( !StorageObject[thisItemsListID] ) {
var itemProperties = {};
itemProperties[thisItemsListID] = {};
itemProperties[thisItemsListID]["property1"] = false;
itemProperties[thisItemsListID]["property2"] = false;
StorageObject.push(itemProperties);
localStorage.setItem('session', JSON.stringify(StorageObject));
}
I can get the data into localStorage using this format but StorageObject[thisItemsListID] always gets into the if statement and generates a duplicate item in localStorage and I'm not sure how to access this with a variable. I'm trying to append the new ID if it doesn't exist so if {1:{} exists but current ID is 2 I need to push the new value.
I'm close here and maybe I need to reevaluate the format I'm storing the data string but I'm going in circles here and could use a point in the right direction.
Well, the duplicate item is happening in StorageObject.push(itemProperties).
Try this to update the object:
//StorageObject.push(itemProperties); <-- remove
StorageObject[thisItemsListID] = itemProperties;
[EDIT]
If you want to keep [{"1":{"property1":false,"property2":false}},{"2":{"property1":false,"property2":false}}]. To conditional would be a bit different.
var haveItem = StorageObject.filter(function(item){
return Objects.keys(item)[0] == thisItemsListID;
}).length > 0;
if ( !haveItem ) {
var itemProperties = {};
itemProperties[thisItemsListID] = {};
itemProperties[thisItemsListID]["property1"] = false;
itemProperties[thisItemsListID]["property2"] = false;
StorageObject.push(itemProperties);
localStorage.setItem('session', JSON.stringify(StorageObject));
}
Are you trying to update the object or just overwrite it? Filipes response illustrates how to update the entire storage object by just reassigning the object with the new value.
If you wanted to update just as section/ value of the object you could do so using a for loop. This would allow you to scan the array locate the one property and then remove it, updated it, overwrite it etc.
Here is an example of the loop. Bear in mind This is a snippet from a report library I was building. It uses angular $scope but it is a complex type doing a similar action to your update (here I am setting a label as a favorite/bookmark)
function OnFavoriteComplete(response) {
var id = response.config.data.reportId; //dynamic values set by client
var isFavorite = response.config.data.isFavorite;//dynamic values set by client
var arrayCount = $scope.reportList.length;
//loop my current collection and look for the property id of the label
//then check to see if true or false/this was a toggle enable disable
if (isFavorite) {
for (var i = 0, iLen = arrayCount; i < iLen; i++) {
if ($scope.reportList[i].reportId == id) {
$scope.reportList[i].isFavorite = false;
}
}
}
//if false update the property with the new value
else {
for (var i = 0, iLen = arrayCount; i < iLen; i++) {
if ($scope.reportList[i].reportId == id) {
$scope.reportList[i].isFavorite = true;
}
}
}
};
If you are using another framework like lowDash it has some really nice helper functions for updating and evaluating arrays.
I have a Kendo UI Grid with a large datasource and paging.
I have an event that fires where I know the underlying data item that I want to select, but am unsure on how to programatically page/select this item in the grid. If the item is not on the current grid page, I cannot use datasource.view() to poke through when the data is not on the current page.
Does anyone know how I can select an item by its underlying data source object?
I've got a similar situation to where i am at #:
http://jsfiddle.net/Sbb5Z/1050/
I can get the data item with the following:
change: function (e) {
var selectedRows = this.select();
var dataItem = this.dataItem(selectedRows[0]);
}
But then I don't know how to select the same row in the other grid.
Basically in the select event of one grid, I want to go select the same item in another grid. These are not the same datasource, as they have different page setups, but it is the same underlying data array.
I have the data item in the target grid -- but I have no clue how to page/select it in the target grid.
Edit:
The best I've come up with sofar is creating a datasource with the same parameters as the original, and paging through it programatically, until I find what I am looking for. Surely there must be a better way?
I've gotten this back from Telerik, and is a little cleaner:
http://jsfiddle.net/RZwQ2/
function findDataItem(theGrid, dataItem) {
//get grid datasource
var ds = theGrid.dataSource;
var view = kendo.data.Query.process(ds.data(), {
filter: ds.filter(),
sort: ds.sort()
})
.data;
var index = -1;
// find the index of the matching dataItem
for (var x = 0; x < view.length; x++) {
if (view[x].Id == dataItem.Id) {
index = x;
break;
}
}
if (index === -1) {
return;
}
var page = Math.floor(index / theGrid.dataSource.pageSize());
var targetIndex = index - (page * theGrid.dataSource.pageSize()) + 1;
//page is 1-based index
theGrid.dataSource.page(++page);
//grid wants a html element. tr:eq(x) by itself searches in the first grid!
var row = $("#grid2").find("tr:eq(" + targetIndex + ")");
theGrid.select(row);
console.log('Found it at Page: ' + page + 'index: ' + targetIndex);
}
You need to have a common id, or field in the data that you can use to uniquely identify the object in the other dataSource, because the kendo generated UID's are not going to be the same accross two different DataSource instances.
Most generally you define the id in the Model you bound to the grid, which you can use to quickly pluck items from the datasource
change: function (e) {
var selectedRows = this.select();
var dataItem = this.dataItem(selectedRows[0]);
var otherItem = otherGrid.dataSource.get(dataItem.id) // will get
}
if you don't have a common ID field specified in the model, but do know how to find the item you can loop through the data source looking for it
var selectedRows = this.select();
var dataItem = this.dataItem(selectedRows[0]);
var data = otherGrid.dataSource.view();
var otherItem;
for ( var i = 0; i < data.length; i++ ){
if( data[i].myCommonField === dataItem.myCommonField ) {
otherItem = data[i];
break;
}
}
UPDATE:
to select the item in the other grid you need to do this:
var elements = otherGrid.items(),
element;
element = elements.filter("[data-uid='" + otherItem.uid + "']")
otherGrid.select(element) // to select just the one item
//OR
otherGrid.select( otherGrid.select().add(element) ) // to add the item to the current selection
I the fiddle you provided uses a really old version of kendo Grid where this won't work...I just realized. are you stuck on the 2011 version? I can probably get something to work at least in theory but the above will work in the newer versions
essentailly you need to match the item you have to a DOM element, in later versions you can use UID because the dom elements all get that on them "data-uid" it looks like if you at id to your model: { } def you can get the tr elements to have data-id which you can use to select the right select using jquery. I use the items()1 method which also doesn't seem to exist on the early version but you can usegrid2.table.find("tr[data-id=]")` instead I believe
Assume div id will be Grid then first we need find the kendoGrid
var grid = $("#Grid").data("kendoGrid");
then call the grid.select() to select the currently selected one
finally call the grid.dataItem() to get the selected item.
var selectedDataItem = grid.dataItem(grid.select());
To expand upon others, I have a method that takes a single (or multiple) ids to match against:
function selectItems(grid, idAr)
{
if(!idAr instanceof Array)idAr = [idAr];
var items = grid
.items()
.filter(function(i, el)
{
return idAr.indexOf(grid.dataItem(el).Id) !== -1;
});
grid.select(items);
}
* Obviously Id could be replaced by any field that is in your data item.
Use for selection:
selectItems(grid, "5");
selectItems(grid, ["6", "7"]);
I have 2 arrays, one with cities, and another array with ID's...
I would like to loop both, and then output some HTML
Right now im only looping cities, and then appending some HTML to a as following:
if ($jq(".area").length > 0) {
var region = $jq(".hiddenFieldRegion").val();
var cityIDs = $jq(".hiddenFieldChosenAreas").val();
var strcities = $jq(".hiddenFieldChosenCities").val();
//var cities = strcities.split('|');
//Set region to be marked
if (region) {
$jq(".mapdk").attr("class", region);
}
//Appending to searchform
if (strcities) {
$jq.each(strcities.toString().split("|"), function (k, v) {
var v = v.split("|");
$jq("<option selected />").text(v[0]).appendTo("select.chosen-cities");
});
}
}
I would like the place where im appending to the searchform, add an attribute value, with an ID from the cityIDs array...
So i get some HTML like:
<option value="XXX">CityName</option>
Is that possible?
Assuming your cities IDs and your cities names are orderred in the same way, you can access each name/id by this index in the arrays :
var cityIDs = $jq(".hiddenFieldChosenAreas").val();
var strcities = $jq(".hiddenFieldChosenCities").val();
var cityIDs_array = cityIds.split("|");
var strcities_array = strcities.split("|");
....
if (strcities) {
$jq.each(strcities_array, function (k, v) {
//var v = v.split("|"); Unnecessary if strcities is like "city1|city2|city3"
$jq("<option value='"+cityIDs_array[k]+"' />").text(v).appendTo("select.chosen-cities");
});
}
In this, way, you can access each city ID in your loop.
But I think you should store city name and city id in the same string. For example "city1:1|city2:2|city3:3". Then, with some use of split function, you'll get id and name.
Assuming the order is the same in both arrays you can use the index parameter (k) of the each function:
if (strcities) {
var aCityIDs = cityIDs.split("|");
$jq.each(strcities.toString().split("|"), function (k, v) {
var v = v.split("|");
var id = aCityIDs[k];
$jq("<option selected value='"+id+"' />").text(v[0]).appendTo("select.chosen-cities");
});
}
This is easy enough if your two arrays are in parity with one another with regards to indexing - loop over one, then look up a value in the other array at the same index.
//simulate environment
var strcities = 'one|two|three',
cityIDs = '1|2|3';
//main code
var ids = cityIDs.split('|');
$.each(strcities.toString().split("|"), function (index, val) {
var opt = $("<option />", {value: ids[index], text: val}).appendTo("select.chosen-cities");
/* if (index == 2) opt.prop('selected', 'selected'); /*
});
A few points of note:
you were splitting on | once in the $.each and again inside it. Since this would be illogical (any | would have disappeared after the first split) I removed it.
you were adding selected to each option. If you simply want the first option to be selected, you don't need to add anything in. If you want a specific option to be selected, see the line I commented out, which selects the 3rd element.
http://jsfiddle.net/DhH3U/
Another example:
var strict = ('aaa|eeee|dddd').split('|');
var id = ('1|2|3').split('|');
$.each(strict, function (k) {
$("<option selected />").text(strict[k]).appendTo("select.chosen-cities").attr('value',id[k]);
});
I'd like to
1) Draw create form fields and populate them with data from javascript objects
2) Update those backing objects whenever the value of the form field changes
Number 1 is easy. I have a few js template systems I've been using that work quite nicely.
Number 2 may require a bit of thought. A quick google search on "ajax data binding" turned up a few systems which seem basically one-way. They're designed to update a UI based on backing js objects, but don't seem to address the question of how to update those backing objects when changes are made to the UI. Can anyone recommend any libraries which will do this for me? It's something I can write myself without too much trouble, but if this question has already been thought through, I'd rather not duplicate the work.
////////////////////// edit /////////////////
I've created my own jquery plugin to accomplish this. Here it is. Please let me know if it's useful and if you think it might be worth making it more "official". Also let me know if you have problems or questions.
/*
Takes a jquery object and binds its form elements with a backing javascript object. Takes two arguments: the object
to be bound to, and an optional "changeListener", which must implement a "changeHappened" method.
Example:
// ============================
// = backing object =
// ============================
demoBean = {
prop1 : "val",
prop2 : [
{nestedObjProp:"val"},
{nestedObjProp:"val"}
],
prop3 : [
"stringVal1",
"stringVal12"
]
}
// ===========================
// = FORM FIELD =
// ===========================
<input class="bindable" name="prop2[1].nestedObjProp">
// ===========================
// = INVOCATION =
// ===========================
$jq(".bindable").bindData(
demoBean,
{changeHappened: function(){console.log("change")}}
)
*/
(function($){
// returns the value of the property found at the given path
// plus a function you can use to set that property
var navigateObject = function(parentObj, pathArg){
var immediateParent = parentObj;
var path = pathArg
.replace("[", ".")
.replace("]", "")
.replace("].", ".")
.split(".");
for(var i=0; i< (path.length-1); i++){
var currentPathKey = path[i];
immediateParent = immediateParent[currentPathKey];
if(immediateParent === null){
throw new Error("bindData plugin encountered a null value at " + path[i] + " in path" + path);
}
}
return {
value: immediateParent[path[path.length - 1]],
set: function(val){
immediateParent[path[path.length - 1]] = val
},
deleteObj: function(){
if($.isArray(immediateParent)){
immediateParent.splice(path[path.length - 1], 1);
}else{
delete immediateParent[path[path.length - 1]];
}
}
}
}
var isEmpty = function(str){
return str == null || str == "";
}
var bindData = function(parentObj, changeListener){
var parentObj,
radioButtons = [];
var changeListener;
var settings;
var defaultSettings = {
// if this flag is true, you can put a label in a field,
// like <input value="Phone Number"/>, and the value
// won't be replaced by a blank value in the parentObj
// Additionally, if the user clicks on the field, the field will be cleared.
allowLabelsInfields: true
};
// allow two forms:
// function(parentObj, changeListener)
// and function(settings).
if(arguments.length == 2){
parentObj = arguments[0];
changeListener = arguments[1]
settings = defaultSettings;
}else{
settings = $jq.extend(defaultSettings, arguments[0]);
parentObj = settings.parentObj;
changeListener = settings.changeListener;
}
var changeHappened = function(){};
if(typeof changeListener != "undefined"){
if(typeof changeListener.changeHappened == "function"){
changeHappened = changeListener.changeHappened;
}else{
throw new Error("A changeListener must have a method called 'changeHappened'.");
}
};
this.each(function(key,val){
var formElem = $(val);
var tagName = formElem.attr("tagName").toLowerCase();
var fieldType;
if(tagName == "input"){
fieldType = formElem.attr("type").toLowerCase();
}else{
fieldType = tagName;
}
// Use the "name" attribute as the address of the property we want to bind to.
// Except if it's a radio button, in which case, use the "value" because "name" is the name of the group
// This should work for arbitrarily deeply nested data.
var navigationResult = navigateObject(parentObj, formElem.attr(fieldType === "radio"? "value" : "name"));
// populate the field with the data in the backing object
switch(fieldType){
// is it a radio button? If so, check it or not based on the
// boolean value of navigationResult.value
case "radio":
radioButtons.push(formElem);
formElem.data("bindDataPlugin", {navigationResult: navigationResult});
formElem.attr("checked", navigationResult.value);
formElem.change(function(){
// Radio buttons only seem to update when _selected_, not
// when deselected. So if one is clicked, update the bound
// object for all of them. I know it's a little ugly,
// but it works.
$jq.each(radioButtons, function(index, button){
var butt = $jq(button);
butt.data("bindDataPlugin").navigationResult.set(butt.attr("checked"));
});
navigationResult.set(formElem.attr("checked"));
changeHappened();
});
break;
case "text":
// if useFieldLabel is true, it means that the field is
// self-labeling. For example, an email field whose
// default value is "Enter Email".
var useFieldLabel = isEmpty( navigationResult.value )
&& !isEmpty( formElem.val() )
&& settings.allowLabelsInfields;
if(useFieldLabel){
var labelText = formElem.val();
formElem.click(function(){
if(formElem.val() === labelText){
formElem.val("");
}
})
}else{
formElem.attr("value", navigationResult.value);
}
formElem.keyup(function(){
navigationResult.set(formElem.attr("value"));
changeHappened();
});
break;
case "select":
var domElem = formElem.get(0);
$jq.each(domElem.options, function(index, option){
if(option.value === navigationResult.value){
domElem.selectedIndex = index;
}
});
formElem.change(function(){
navigationResult.set(formElem.val());
})
break;
case "textarea":
formElem.text(navigationResult.value);
formElem.keyup(function(){
changeHappened();
navigationResult.set(formElem.val());
});
break;
}
});
return this;
};
bindData.navigateObject = navigateObject;
$.fn.bindData = bindData;
})(jQuery);
There are tons of libraries out there to achieve what you want.
For starters, you can use DWR to get the Ajax functionality. The method that gets triggered for the form field's onChange event should make a DWR call to the corresponding backing object
Hope this helps!
Microsoft has a proposal for an enhancement to jQuery core that implements decently reach data binding including a two binding (source to target, and target to source).
They call it Data Linking - which is odd since the name of this concept in all other MS technologies (WinForms, WPF, WebForms, etc...) is DataBinding.
Link to the MS Proposal
DataBind - a template plugin for jQuery. The library has a method unbinddata(), which collects the data back to json-array.
Unfortunately, the library works only with form inputs.
If you made some solution, can I see it?
I have a HTML select list with quite a few (1000+) names. I have a javascript in place which will select the first matching name if someone starts typing. This matching looks at the start of the item:
var optionsLength = dropdownlist.options.length;
for (var n=0; n < optionsLength; n++)
{
var optionText = dropdownlist.options[n].text;
if (optionText.indexOf(dropdownlist.keypressBuffer,0) == 0)
{
dropdownlist.selectedIndex = n;
return false;
}
}
The customer would like to have a suggest or autofilter: typing part of a name should 'find' all names containing that part. I've seen a few Google Suggest like options, most using Ajax, but I'd like a pure javascript option, since the select list is already loaded anyway. Pointers anyone?
Change
if (optionText.indexOf(dropdownlist.keypressBuffer,0) == 0)
to
if (optionText.indexOf(dropdownlist.keypressBuffer) > 0)
To find dropdownlist.keypressBuffer anywhere in the optionText.
The YUI libraries have a library for this sort of functionality, called AutoComplete.
The DataSource for AutoComplete can be local javascript objects, or can be easily switched to Ajax if you change your mind.
The YUI components are pretty customizable with a fair bit of functionality.
Edit: I'm not sure if you can get it to work with a select box as required by the question though. Might be possible.
I would set up a cache to hold the options inside my select. And instead of filtering options in the select, I would clear the select, and re-populate it with matched options.
Pseudo-code galore:
onLoad:
set cache
onKeyPress:
clear select-element
find option-elements in cache
put found option-elements into select-element
Here's a little POC I wrote, doing filtering on selects from what is selected in another select--in effect chaining a bunch of selects together.
Perhaps it can give you a few ideas:
function selectFilter(_maps)
{
var map = {};
var i = _maps.length + 1; while (i -= 1)
{
map = _maps[i - 1];
(function (_selectOne, _selectTwo, _property)
{
var select = document.getElementById(_selectTwo);
var options = select.options;
var option = {};
var cache = [];
var output = [];
var i = options.length + 1; while (i -= 1)
{
option = options[i - 1];
cache.push({
text: option.text,
value: option.value,
property: option.getAttribute(_property)
});
}
document.getElementById(_selectOne).onchange = function ()
{
var selectedProperty = this
.options[this.selectedIndex]
.getAttribute(_property);
var cacheEntry = {};
var cacheEntryProperty = undefined;
output = [];
var i = cache.length + 1; while (i -= 1)
{
cacheEntry = cache[i - 1];
cacheEntryProperty = cacheEntry.property;
if (cacheEntryProperty === selectedProperty)
{
output.push("<option value=" + cacheEntry.value + " "
_property + "=" + cacheEntryProperty + ">" +
cacheEntry.text + "</option>");
}
}
select.innerHTML = output.join();
};
}(map.selectOne, map.selectTwo, map.property));
}
}
$(function ()
{
selectFilter([
{selectOne: "select1", selectTwo: "select2", property: "entityid"},
{selectOne: "select2", selectTwo: "select3", property: "value"}
]);
});
use this filter script
http://www.barelyfitz.com/projects/filterlist/