knockout and Select2 get selected object - javascript

I'm working on a project where im using .net web api, knockout and in this example, the jquery plugin select2.
What im trying to do is to set some field values after the change of the selection.
The select2 control list is loaded after ajax call and the objects contain more data than just id and text.
How can i get the rest of the data, so i can fill the other inputs with it?
Shortly, im trying to update a viewmodel after the change of the selection (but i get the data when this plugin makes the ajax call).
Here is a sample data that the selected object should contain:
{
"Customer":{
"ID":13,
"No":"0000012",
"Name":"SomeName",
"Address":"SomeAddress",
"ZipCode":"324231",
"City":"SimCity",
"Region":"SS",
"Phone":"458447478",
"CustomerLocations":[]
}
}
Here is where i am for now:
Sample html:
<input type="hidden" data-bind="select2: { optionsText: 'Name', optionsValue: 'ID', sourceUrl: apiUrls.customer, model: $root.customer() }, value: CustomerID" id="CustomerName" name="CustomerName" />
<input type="text" data-bind="........" />
<input type="text" data-bind="........" />
etc...
and this is the custom binding:
ko.bindingHandlers.select2 = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var obj = valueAccessor(),
allBindings = allBindingsAccessor();
var optionsText = ko.utils.unwrapObservable(obj.optionsText);
var optionsValue = ko.utils.unwrapObservable(obj.optionsValue);
var sourceUrl = ko.utils.unwrapObservable(obj.sourceUrl);
var selectedID = ko.utils.unwrapObservable(allBindings.value);
var model = ko.utils.unwrapObservable(obj.model);//the object that i need to get/set
$(element).select2({
placeholder: "Choose...",
minimumInputLength: 3,
initSelection: function (element, callback) {
if (model && selectedID !== "") {
callback({ id: model[optionsValue](), text: model[optionsText]() });
}
},
ajax: {
quietMillis: 500,
url: sourceUrl,
dataType: 'json',
data: function (search, page) {
return {
page: page,
search: search
};
},
results: function (data) {
var result = [];
$.each( data.list, function( key, value ) {
result.push({ id: value[optionsValue], text: value[optionsText] });
});
var more = data.paging.currentPage < data.paging.pageCount;
return { results: result, more: more };
}
}
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).select2('destroy');
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var obj = valueAccessor(),
allBindings = allBindingsAccessor();
var model = ko.utils.unwrapObservable(obj.model);//the object that i need to get/set
var selectedID = ko.utils.unwrapObservable(allBindings.value);
$(element).select2('val', selectedID);
$(element).on("change", function (e) {
//...
});
}
};
Getting the selected id or text is not a problem, but how not to loose the rest of the information after the ajax call?
Where can i set/get this object so i can have all the data that it contains?
Thank you

When you build a object literal for your results, added the full object as a "data" property.
result.push({ id: value[optionsValue], text: value[optionsText], data: value });
Then handle the select2-selected event thrown by select2. The event object this should contain your object literal as the choice property.
$element.on('select2-selected', function(eventData) {
if ( eventData.choice ) {
// item selected
var dataObj = eventData.choice.data;
var selectedId = eventData.choice.id;
} else {
// item cleared
}
});

For select2 v4, you can use $(elem).select2('data') to get the selected objects.
$('selected2-enabled-elements').on('change', function(e) {
console.log($(this).select2('data'));
});
Example: https://jsfiddle.net/calvin/p1nzrxuy/

For select2 versions before v4.0.0 you can do:
.on("select2-selecting", function (e) {
console.log(e.object);
})
From v4.0.0 on and upwards the following should work:
.on("select2-selecting", function (e) {
$(this).select2('data')
})

Related

Netlify CMS custom widget not working with Map?

I've been trying to create a custom widget for Netlify CMS to allow for key-value pairs to be inserted. However there are a few things going wrong, I'm thinking they might be related so I'm making a single question about them.
This is my first custom widget, and I’m mostly basing this on the official tutorial: https://www.netlifycms.org/docs/custom-widgets/
I’m using a Map as the value, but when I add a new element to the map, and then call the onChange(value) callback, nothing seems to happen. However, if I change it to onChange(new Map(value)) it does update. It seems that the onChange callback requires a new object?
Secondly, the value doesn’t seem to be actually saved. When I fill in other widgets and refresh the page, it then asks to restore the previous values. However it doesn’t restore the map, while restores the other values just fine.
And lastly, I get uncaught exception: Object like a second after I change anything to the map. My guess is that Netlify CMS is trying to save the map (debouncing it for a second so it doesn’t save every letter I type), but fails and throws that exception. That would explain the previous problem (the non-saving one).
My complete code for the custom widget currently is:
var IngredientsControl = createClass({
getDefaultProps: function () {
return {
value: new Map()
};
},
addElement: function (e) {
var value = this.props.value;
value.set("id", "Description");
//is.props.onChange(value);
this.props.onChange(new Map(value));
},
handleIdChange: function (oldId, newId) {
console.log(oldId, newId);
var value = this.props.value;
var description = value.get(oldId);
value.delete(oldId);
value.set(newId, description);
//this.props.onChange(value);
this.props.onChange(new Map(value));
},
handleDescriptionChange: function (id, description) {
console.log(id, description);
var value = this.props.value;
value.set(id.toLowerCase(), description);
//this.props.onChange(value);
this.props.onChange(new Map(value));
},
render: function () {
var value = this.props.value;
var handleIdChange = this.handleIdChange;
var handleDescriptionChange = this.handleDescriptionChange;
var items = [];
for (var [id, description] of value) {
var li = h('li', {},
h('input', { type: 'text', value: id, onChange: function (e) { handleIdChange(id, e.target.value); } }),
h('input', { type: 'text', value: description, onChange: function (e) { handleDescriptionChange(id, e.target.value); } })
);
items.push(li);
}
return h('div', { className: this.props.classNameWrapper },
h('input', {
type: 'button',
value: "Add element",
onClick: this.addElement
}),
h('ul', {}, items)
)
}
});
var IngredientsPreview = createClass({
render: function () {
var value = this.props.value;
var items = [];
for (var [id, description] of value) {
var li = h('li', {},
h('span', {}, id),
h('span', {}, ": "),
h('span', {}, description)
);
items.push(li);
}
return h('ul', {}, items);
}
});
CMS.registerWidget('ingredients', IngredientsControl, IngredientsPreview);
What am I doing wrong?
Thanks!
I solved this by using immutable-js's map: https://github.com/immutable-js/immutable-js

Get data from php file to javascript

The javascript below is supposed to be some sort of autocomplete. I am using bootstrap typeahead.
When I type items in my input field, I am able to see suggestions, the problem is I am not able to select them and populate the input field.
Any idea what may be wrong with it?
<script type="text/javascript">
$('#typeahead').typeahead({
source: function (query, process) {
objects = [];
map = {};
return $.get('live_search.php?filter=relation', { query: query }, function (data) {
console.log(data);
var data = $.parseJSON(data);
return process(data);
});
$.each(data, function(i, object) {
map[object.name] = object;
objects.push(object.name);
});
process(objects);
},
updater: function(item) {
$('#getSelection').val(map[item].name);
$('#getValue').val(map[item].name);
return item;
}
});
</script>
It looks like your source method returns immediately from the $.get call and never enters the $.each iteration. Move the iteration code into the get request callback block.
Also you're references to objects and map are in the scope of the source method, but you reference them in the updater method
var typeahead = {
objects: [],
map: {},
};
$("#typeahead").typeahead({
source: function(query, process) {
return $.get("live_search.php?filter=relation", { query: query }, function(
data
) {
var data = $.parseJSON(data);
$.each(data, function(i, object) {
typeahead.map[object.name] = object;
typeahead.objects.push(object.name);
});
return process(data);
});
},
updater: function(item) {
$("#getSelection").val(typeahead.map[item].name);
$("#getValue").val(typeahead.map[item].name);
return item;
},
});

iCheck Plug in & Knockout Js

I need some help to check a checkbox on page load using knockout & icheck plug in.
I have created a custom binding in order to listen to 'ifChecked' method of check but it's not working.
<input type="checkbox" id="access-user-information" name="edit_existing_user" data-bind = "iCheck: { checked: selectedUser() && selectedUser().edit_existing_user==1}">
Knockout Code:
ko.bindingHandlers.iCheck = {
init: function (element, valueAccessor) {
$(element).iCheck({
checkboxClass: 'icheckbox_square-red'
});
$(element).on('ifChecked', function (event) {
var observable = valueAccessor();
observable.checked(true);
});
},
update: function (element, valueAccessor) {
var observable = valueAccessor();
}
};
Some changes:
Listen for a change at the checkbox ('ifToggled' event)
The observable of a checkbox should be a boolean, so set the value according to the checkbox state (true/false). I used the jQuery .is(':checked') for that.
Set the initial state in the "update" function.
The code for the binding:
ko.bindingHandlers.iCheck = {
init: function(element, valueAccessor) {
$(element).iCheck({
checkboxClass: 'icheckbox_flat-red'
});
$(element).on('ifToggled', function(event) {
var observable = valueAccessor();
observable($(element).is(':checked'));
});
},
update: function(element, valueAccessor) {
var observable = valueAccessor();
observable($(element).is(':checked'));
}
};
Here a little jsbin to test it.
Hope that helps.
If your checkbox is enabled, and you can give it a new value by tapping it, it has to be a ko.computed with a read and write method.
When you select the current user, you want it to automatically be checked. This is the read part:
this.isEditingCurrentUser = ko.computed(function() {
return this.selectedUser() &&
this.selectedUser().edit_existing_user === 1;
}, this);
You should recognise this expression: it's what you used as your data-bind. Having a ko.computed in your viewmodel is very similar to using expressions in data-binds.
Now, there's a problem when you want to override this value with true or false: knockout can't decide for you how to reverse the logic. This, you have to specify. It'll look like this:
this.isEditingCurrentUser = ko.computed({
read: function() { /* the expression above */ },
write: function(newValue) { /* How to handle a user input override */ }
});
I show how this works in the example below. I've left out the custom binding since the real logic problem needs to be solved first:
var VM = function() {
var currentUserId = 1;
this.users = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
];
this.selectedUser = ko.observable(2);
this.editCurrent = ko.computed({
read: function() {
return this.selectedUser() &&
this.selectedUser().id === currentUserId;
}.bind(this),
write: function(val) {
var newUser = val
? this.users.find(function(user) {
return user.id === currentUserId;
})
: null;
this.selectedUser(newUser);
}.bind(this)
}, this);
};
ko.applyBindings(new VM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: users, value: selectedUser, optionsCaption: 'Select a user', optionsText: 'name'"></select>
<div>
<input type="checkbox" data-bind="checked: editCurrent">
Edit current user
</div>

how to access changed attributes after fetch? backbone.js

I'm using this code to fetch a model from a server:
var id = args.model.get('id');
var options = {
action: 'getSubscriber',
address: args.model.get('address'),
silent: true
};
new Subscriber({ id: id }, options).fetch({
success: function(model, response) {
console.log(response);
console.log(model);
}
});
the response object contains all the data I need whereas model stores the data not as its direct attributes but as changed object. Is it wrong?
Usually I access model attributes with help of model.get('name') call. How do I access fresh attributes in that case? Should it be model.changed.thePropertyIwantToAccess?
You can use this change event
this.model.on('change', function () {
var changedAttributes = this.model.changedAttributes();
//Process the changed attributes
}, this);
Bind this events in the initialize function of the View
Ended up with this:
var Subscriber = Backbone.Model.extend({
defaults: {
id: null,
name: null,
status: null
// ...
},
initialize: function(attributes, options) {
var _this = this;
this.options = options || {};
this.on('change', function() {
_this.set(_this.changedAttributes()['0']);
});
}
// ...

Knockoutjs select value change doesn't update observable

I have a select option that gets its initial value from EmailField and its options from allEmailFields:
<select data-bind="options: $parent.allEmailFields, value: EmailField()"></select>
When I change the value of the select my model doesn't get updated. Isn't this something two way binding should take care of? Or I need to write handler for the change event?
Module is here:
define('mods/fieldmapping', ["knockout", "libs/knockout.mapping", "datacontext", "mods/campaigner", "text!templates/fieldmapping.html", "text!styles/fieldmapping.css"],
function (ko, mapping, datacontext, campaigner, html, css) {
'use strict';
var
fieldMappingItem = function (data) {
var self = this;
self.CrmField = ko.observable(data.CrmField);
self.EmailField = ko.observable(data.EmailField);
},
dataMappingOptions = {
key: function (data) {
return data.PlatformFieldName;
},
create: function (options) {
return new fieldMappingItem(options.data);
}
},
fieldMappingViewModel = {
contentLoading: ko.observable(false)
},
showFieldMapping = function () {
campaigner.addStylesToHead(css);
campaigner.addModalInnerPanel(html);
},
init = function (connectionId, connectionName) {
fieldMappingViewModel.fieldMappings = mapping.fromJS([]);
fieldMappingViewModel.allEmailFields = mapping.fromJS([]);
fieldMappingViewModel.originatorConnectionName = ko.observable();
fieldMappingViewModel.originatorConnectionName(connectionName);
fieldMappingViewModel.saveFieldMappings = function () {
console.log(ko.toJSON(fieldMappingViewModel.fieldMappings));
amplify.request("updateExistingFieldMappings",
{
cid: connectionId,
RequestEntity: ko.toJSON(fieldMappingViewModel.fieldMappings)
},
function (data) {
console.log(data);
});
};
showFieldMapping();
amplify.request('getExistingFieldMappings', { cid: connectionId }, function (data) {
amplify.request("getCampaignerFields", function (breezerData) {
mapping.fromJS(breezerData.ResponseEntity, fieldMappingViewModel.allEmailFields);
});
mapping.fromJS(data.ResponseEntity, dataMappingOptions, fieldMappingViewModel.fieldMappings);
ko.applyBindings(fieldMappingViewModel, $('#fieldMapping')[0]);
});
};
return {
init: init,
fieldMappingViewModel: fieldMappingViewModel,
html: html,
css : css
}
});
Replace:
<select data-bind="options: $parent.allEmailFields, value: EmailField()"></select>
With:
<select data-bind="options: $parent.allEmailFields, value: EmailField"></select>
if you want to create bi-derectinonal dependency, so you should pass into binding observable.
P.S.: http://knockoutjs.com/documentation/observables.html#observables

Categories