UI-Select2 binding to object instead of property of object - javascript

I am using ui-select2, version 3.5.2, trying to do a single select, type-ahead and retrieve from REST api, drop down list.
It looks like it is working except for one major issue, which is that, the ng-model's property gets set to an object {Id: "some id", text: "some text"} instead of the actual Id property. I cannot figure out how to tell ui-select2 control to set the ng-model property to the "Id" field of the object, instead of the whole object.
I have tried various hacks with watchers but didnt get anywhere. I am sure there is something that I am missing because this is something that should be possible easily.
Here is my javascript code:
$scope.selectOptions = {
placeholder: '- Select Value -',
allowClear: true,
minimumInputLength: 2,
initSelection: function (element, callback)
{
if ($scope.myobj && $scope.myobj.Id && $scope.myobj.Id !== '00000000-0000-0000-0000-000000000000')
{
$.ajax("../../api/objs/" + $scope.myobj.Id).done(function (data) {
var res = $(data).map(function (i, o) {
return {
id: o.Value,
text: o.Display
};
}).get();
callback(res[0]);
});
}
},
ajax:
{
type: "GET",
url: function (term) {
return ["../../api", "objs", encodeURIComponent(term)].join("/");
},
dataType: "json",
contentType: "application/json",
cache: false,
results: function (data, page) {
return {
results: $(data).map(function (i, o) {
angular.extend(o, {
id: o.Value,
text: o.Display
});
return o;
}).get()
};
}
}
}
Here is my html code:
<div ui-select2='selectOptions' ng-model="myobj.Id" style="width:215px" />

I got around this by binding to a separate property and then adding a ng-change on my div and syncing the binding property's id field to actual property on my object.

Related

Bind OracleJet ojtimeline component to viewModel

I am trying to understand how I can bind data from the view-model to the view. The REST request to the back-end is working fine and I get a JSON array with several items. The existing documentation doesn't give me enough help.
How can I bind the timeline component ojtimeline to the view-model data array?
Edit: No errors now, since the view recognize the view-model array. But the ojtimeline doesn't display the data, only a working empty view component.
View
<div id="tline"
data-bind='ojComponent: {
component: "ojTimeline",
minorAxis: {
scale: "hours",
zoomOrder: ["hours", "days", "weeks"]
},
majorAxis: {
scale: "weeks"
},
start: new Date("Jan 1, 2016").toISOString(),
end: new Date("Jun 31, 2016").toISOString(),
referenceObjects: [{value: new Date("Feb 1, 2010").toISOString()}],
series: [{
id: "id",
emptyText: "No Data.",
items: statusArray,
label: "Oracle Events"
}],
overview: {
rendered: "off"
}
}' style="width: '100%';height: 350px"></div>
View-model
define(['ojs/ojcore', 'knockout', 'jquery', 'ojs/ojknockout', 'ojs/ojtimeline'],
function (oj, ko) {
/**
* The view model for the main content view template
*/
function timelineContentViewModel() {
var self = this;
this.statusArray = ko.observableArray([]);
self.addData = function () {
$.ajax({
url: "http://localhost:8080/myproject/rest/status/v1/findAll",
type: 'GET',
dataType: 'json',
success: function (data, textStatus, jqXHR) {
var x = data;
for (i = 0; i < x.length; i++) {
statusArray.push({
id: data[i].id,
description: data[i].text,
title: data[i].user.screenName,
start: data[i].createdAt});
}
//$("#tline").ojTimeline("refresh"); Doesn't have ant affect
}
});
};
self.addData();
}
return timelineContentViewModel;
});
The ReferenceError is caused by
var statusArray = ko.observableArray([]);
it should be
this.statusArray = ko.observableArray([])
You will also (probably) need to refresh the timeline when the observable array has changed, e.g. after the for-loop in success callback:
...
success: function (data, textStatus, jqXHR) {
var x = data;
for (i = 0; i < x.length; i++) {
self.statusArray.push({
id: data[i].id,
description: data[i].text,
title: data[i].user.screenName,
start: data[i].createdAt});
}
$("#tline").ojTimeline("refresh");
}
...
I have loaded ojTimeline from Ajax data and have never needed to use refresh. Worst case, you can wrap the ojTimeline in a <!-- ko if ... --> so that the timeline doesn't appear until you have an Ajax response.
For the ojTimeline items attribute, instead of referencing the observable, I had to unwrap the observable like this: items: ko.toJS(statusArray).
Another thing to consider is pushing into an ko.observableArray inside a for loop. Each push using the ko.observableArray push() method invokes subscriptions. If your array is bound to the UI, then each push will trigger a DOM change. Instead, it is often better to push into the underlying array (unwrap the array) and then invoke self.statusArray.valueHasMutated. You may also want to keep an eye on your use of this, self, and nothing. Consistency will help avoid bugs like the one ladar identified.
What do you think about rewriting your for loop like this (code untested)?
ko.utils.arrayPushAll(
self.statusArray(),
ko.utils.arrayMap(data, function(item) {
return {
id: item.id,
description: item.text,
title: item.user.screenName,
start: item.createdAt;
};
});
);
self.statusArray.valueHasMutated();
Or, if you can get away with it (some OJ components don't like this approach), you can skip the push and just replace the entire array inside the observable:
self.statusArray(
ko.utils.arrayMap(data, function(item) {
return {
id: item.id,
description: item.text,
title: item.user.screenName,
start: item.createdAt;
};
});
);

Select2: Update option after selecting new tag

I implemented a tagging system where you can choose from existing tags or add new tags. After a new tag has been selected it will persisted using an AJAX call.
For achieving this I use the callback createTag and the event select2:select. Because I like to create the tag only when it is selected I do an AJAX call for this if the event select2:select gets triggered.
The problem is that I need to update the already created option of select2 with the ID I get from persisting my new tag to the database. What's the cleanest solution for this?
Here's what I have:
$('select.tags').select2({
tags: true,
ajax: {
url: '{{ path('tag_auto_complete') }}',
processResults: function (data) {
return {
results: data.items,
pagination: {
more: false
}
};
}
},
createTag: function (tag) {
return {
id: tag.term, // <-- this one should get exchanged after persisting the new tag
text: tag.term,
tag: true
};
}
}).on('select2:select', function (evt) {
if(evt.params.data.tag == false) {
return;
}
$.post('{{ path('tag_crrate_auto_complete') }}', { name: evt.params.data.text }, function( data ) {
// ----> Here I need to update the option created in "createTag" with the ID
option_to_update.value = data.id;
}, "json");
});
My problem was that I did not add the new tag as an <option> tag to the native select field.
This is necessary because select2 checks for the values set trough select2.val(values) if an <option> tag with this value does exist. If not select2 silently throws the value out of the array and sets the array of values which have a corresponding option tag in the underlying select field.
So this is how it works correct now (for select2 4.0.x):
$('select.tags').select2({
tags: true,
ajax: {
url: '{{ path('tag_auto_complete') }}',
processResults: function (data) {
return {
results: data.items,
pagination: {
more: false
}
};
}
},
createTag: function (tag) {
return {
id: tag.term,
text: tag.term,
tag: true
};
}
}).on('select2:select', function (evt) {
if(evt.params.data.tag == false) {
return;
}
var select2Element = $(this);
$.post('{{ path('tag_crrate_auto_complete') }}', { name: evt.params.data.text }, function( data ) {
// Add HTML option to select field
$('<option value="' + data.id + '">' + data.text + '</option>').appendTo(select2Element);
// Replace the tag name in the current selection with the new persisted ID
var selection = select2Element.val();
var index = selection.indexOf(data.text);
if (index !== -1) {
selection[index] = data.id.toString();
}
select2Element.val(selection).trigger('change');
}, 'json');
});
The minimal AJAX response (JSON format) has to look like this:
[
{'id': '1', 'text': 'foo'},
{'id': '2', 'text': 'bar'},
{'id': '3', 'text': 'baz'}
]
You may add additional data to each result for let's say own rendering of the result list with additional data in it.
Just to update:
The new syntax is
e.params.args.data.id
not
e.params.data.id

Select2 inserts an empty text option in the dynamic list

I am using Select2 with Jquery-editable and encountering an abnormal behavior of Select2, what I am doing is displaying editable table of information using ejs template, and as user clicks on CBA opens up a select2 box which have the originally selected result, and then user can add or delete options in it, options comes from Database source, and when user selects an options it adds an empty option in database with the selected option , the array looks like this
[ "ABCD", "ONAB", "" , "BCNU" ]
I read somewhere about allowClear: true and add a placeHolder but It doesn't helped me at all. As everything is done dynamically I can't find where that empty option is added.
Code is below:
Ejs/HTML code for Select 2
<tr>
<td width="40%">Select CBA(s)</td>
<td>
<a class="cbaSelectUnit" data-emptytext="Select CBA(s)" data-original-title="Select CBA(s)" data-type="select2"></a>
</td>
Javascript for Select 2
$("a[data-name='Cba']").editable({
showbuttons: 'false',
emptytext: 'None',
display: function(values) {
var html = [];
html.push(values);
$(this).html(html);
},
select2: {
multiple: true,
allowClear: true,
placeholder: "Select CBA(s)",
ajax: {
// url is copied from data-source via x-editable option-passing mechanism
dataType: 'json',
// pass the '?format=select2' parameter to API call for the select2-specific format
data: function(term, page) {
return {
deptId: departmentId,
format: 'select2'
};
},
// transform returned results into the format used by select2
results: function(data, page) {
return {
results: data
};
}
},
// what is shown in the list
formatResult: function(cba) {
return cba.text;
},
// what will appear in the selected tag box
formatSelection: function(cba) {
return cba.text;
},
// rendering id of the values to data.value requirement for Select 2
id: function(cba) {
return cba.value;
},
// what is shown in the selected-tags box
initSelection: function(element, callback) {
var id = $(element).val(),
result = id.replace(/^,\s*$/, ',').split(",").map(function(v) {
return {
id: v,
text: v
};
});
callback(result);
}
}
});
Format in which Code is returned from the database:-
Facility.findOne({ _id: department.Facility }, function(err, facility) {
if (err) {
res.send(500, err);
} else if (!facility) {
res.send(404, 'Facility not found');
} else if (req.query.format && req.query.format === 'select2') {
var result = facility.Cba.map(function(c) {
return { value: c, text: c };
});
res.json(result);
}
});
Image showing an empty box added by itself
How Array looks after I edit
So it was just a simple syntax error, I was doing found out by myself,
I was returning cba.value as id, but the initSelection was returning
{id: v, text: v}
it should be value & text instead of id & text.
// what is shown in the selected-tags box
initSelection: function(element, callback) {
var id = $(element).val(),
result = id.replace(/^,\s*$/, ',').split(",").map(function(v) {
return {
value: v,
text: v
};
});
callback(result);
}

Extending kendo multiselect and working with MVVM

I'm trying to extend a kendo multiselect so that it has a default data source as well as templates. It's all working except the pre-loaded values in the MVVM object. When I start updating the exented multiselect, the value of the MVVM gets updated, the initial items are just not pre-loaded:
kendo.ui.plugin(kendo.ui.MultiSelect.extend({
init: function(element, options) {
var ds = new kendo.data.DataSource({
type: "json",
serverFiltering: true,
transport: {
read: {
url: "/SecurityEntities",
dataType: "json"
},
parameterMap: function(data) {
return {
prefixText: '',
count: 5,
getUsers: true,
getGroups: false
};
}
},
schema: {
data: function(data) {
console.log($.parseJSON(data));
return $.parseJSON(data);
}
}
});
options = options == null ? {} : options;
options.itemTemplate = "...";
options.tagTemplate = "...";
options.dataSource = ds;
kendo.ui.MultiSelect.fn.init.call(this, element, options);
},
options: {
name: 'EntityMultiSelect'
}
}));
kendo.data.binders.widget.entitymultiselect =
kendo.data.binders.widget.multiselect;
Then my html is:
<div data-bind="value: machine.Managers" data-role="entitymultiselect"
data-delay="400" data-animation="false"
data-placeholder="Select users to notify"></div>
And I am binding the whole container to the page's viewModel object.
I've seen other people have issues with this very problem and added the
kendo.data.binders.widget.entitymultiselect =
kendo.data.binders.widget.multiselect;
(And yes that does seem like a bug)
But it still doesn't work.
When there are already values in Machine.Managers, it doesn't load them. However, if I add values to the multiselect, they get added to Machine.Managers .
EDIT:
I've added a live example
At least in your demo it's a trivial problem: your data-value-field is wrong. As a result, the binder can't match the selected elements.
Instead of
<div data-role="entitymultiselect"
data-bind="value: selected"
data-value-field="ProductId"></div>
you need
<div data-role="entitymultiselect"
data-bind="value: selected"
data-value-field="ProductID"></div>
(working demo)
Since you're not defining the value field in the code in your question, it might be the same issue.

Select2 - avoiding duplicates tags

How can I avoiding duplicates tags in Select2 input?
When I type tag name on the keyboard string is added to input field, but when I select tag from dropdown list (results from the database) the id is added to input (look at console.log on screenshot). So I can select tag from list and add the same tag from keyboard.
Moreover, I need the text of tags, not id from dropdown list while submit a form.
Full resolution
HTML:
<input type="hidden" id="categories" name="categories" style="width:100%" value="${categories}">
JS:
$("#categories").select2({
tags: true,
tokenSeparators: [","],
placeholder: "Dodaj",
multiple: false,
minimumInputLength: 3,
maximumInputLength: 50,
maximumSelectionSize: 20,
ajax: {
quietMillis: 150,
url: '${request.route_url("select2")}',
dataType: 'json',
data: function (term, page) {
return {
q: term,
page_limit: 10,
page: page,
};
},
results: function (data, page) {
var more = (page * 10) < data.total;
return {results: data.categories, more: more};
}
},
initSelection: function (element, callback) {
var data = [];
$(element.val().split(",")).each(function () {
data.push({id: this, text: this});
});
callback(data);
},
createSearchChoice: function (term) {
return { id: term, text: term };
},
}).change(function (e) {
if (e.added) {
console.log($("#categories").val())
console.log(e)
}
});
Have same problem, but I figured it out to find a way around.
I'm getting text and ids, but on the server side I'm creating from given id new objects, which are well read.
$tagsArray = explode(',', $tagNames); // it contains of my input select2 value (with ids)
foreach ($tagsArray as $tag)
{
if (is_numeric($tag))
{
$tags->append(TagQuery::create()->filterById($tag)->findOneOrCreate());
}
elseif (!empty($tag))
{
$tags->append(TagQuery::create()->filterByName($tag)->findOneOrCreate());
}
}
Hope it helps.
at first use select 2
and then do this:
$("select").change(function() { var tr = $(this).closest("tr");
tr.find("select option").attr("disabled",""); //enable everything
//collect the values from selected;
var arr = $.map
(
tr.find("select option:selected"), function(n)
{
return n.value;
}
);
//disable elements
tr.find("select option").filter(function()
{
return $.inArray($(this).val(),arr)>-1; //if value is in the array of selected values
}).attr("disabled","disabled"); });

Categories

Resources