I have created a custom element in Aurelia.
import {bindable, inject, customElement, bindingMode} from 'aurelia-framework';
import 'select2';
import * as $ from 'jquery';
import {BindingSignaler} from "aurelia-templating-resources";
#customElement('select2')
#inject(Element, BindingSignaler)
export class Select2CustomMultiselect {
#bindable name = null; // name/id of custom select
#bindable selected = null; // default selected values
#bindable ({defaultBindingMode: bindingMode.oneWay, attribute:"options"}) source:Array<{id:number, name:any}>= []; // array of options with id/name properties
#bindable placeholder = "";
#bindable allow_clear = true;
private $select2: $;
constructor(private element, private signaler:BindingSignaler) {
}
attached() {
let $select = $(this.element).find('select');
this.$select2 = $select.select2({theme: 'bootstrap', placeholder: this.placeholder});
// on any change, propagate it to underlying select to trigger two-way bind
this.$select2.on('change', (event) => {
if (event.originalEvent) { return; }
const select2Value = this.$select2.val();
if(select2Value == this.selected) { return; }
// dispatch to raw select within the custom element
var notice = new Event('change', {bubbles: true});
event.target.dispatchEvent(notice);
});
this.$select2.val(this.selected);//.trigger('change');
}
selectedChanged(newValue,oldValue){
console.log(newValue);
}
detached() {
$(this.element).find('select').select2('destroy');
}
}
And it's template:
<template>
<select value.two-way="selected" name.one-way="name" id.one-way="name" class="form-control" data-allow-clear.one-way="allow_clear" size="1">
<option></option>
<option repeat.for="src of source" model.bind="src.id">${src.name & t}</option>
</select>
</template>
I use the control like this:
<select2 name="payingBy" selected.two-way="model.countryId & validate" options.bind="countries" placeholder="${'Select' & t}" allow_clear="true"></select2>
And model is:
countries:Array<{id:number, name:string}> = [{id:1, name:"USA"}, {id:2, name:Canada'}];
model.countryId: number;
Now, everything works fine if I change the select and on initial binding.
But if i change the model.countryId from ie. 1 to 2, the change is not reflected in the select control, the control still displays "USA" as like 1 is selected.
Because 'selected' property is bind two-way I would expect it to update the select when it change. But it does not. Why?
What Am I doing wrong?
Please help
Ok, I implemented it like in this post:Custom Select2 Aurelia component
And it works perfectly.
That is because you are using the data version which expects an object, but you have set your select to work with the id value only. So you should use the val to pass the id.
selectedChanged(newValue, oldValue) {
console.log(newValue);
if (this.select2) {
this.select2.select2({
val: newValue, // << changed this from data: newValue
theme: 'bootstrap',
placeholder: this.placeholder
});
}
I am using the custom binding provided in How to create an auto-complete combobox?
I want to allow the user to either select a value from the list of suggestions or enter a value that is not in the list of suggestions. How can I get the value of the input into my observable field?
For example, if the user types 'smi' the autocomplete list will show Smith and other surnames beginning with 'smi', however, if they do not select an option from the list, I just want to set the value of my observable field to be 'smi'. At present, the only way the observable propety is set is when the user selects an item from the list of suggestions.
I have the following code (HTML):
<input type="text" data-bind="
value: surname,
jqAuto: { autoFocus: true },
jqAutoSource: surnames,
jqAutoQuery: surnameAutocomplete,
jqAutoValue: surname"
/>
JavaScript view model (simplified):
var vm = {
surnames: ko.observableArray(),
surname: ko.observable(),
surnameAutocomplete: function (searchTerm, result) {
repository.surnameAutocomplete(searchTerm, result);
};
Solution:
I amended the custom binding handler in two places:
init: function - added the following
// New setting to allow / disallow a user to enter values that are in the autocomplete list.
forceSelection = allBindings.jqAutoForceSelection;
options change function - amended to the following
//on a change, make sure that it is a valid value or clear out the model value
options.change = function (event, ui) {
var currentValue = $(element).val();
// Start: new code, check new setting on whether to force user to make a selection
if (!forceSelection) {
writeValueToModel(currentValue);
return;
}
// End: new code
var matchingItem = ko.utils.arrayFirst(unwrap(source), function (item) {
return unwrap(inputValueProp ? item[inputValueProp] : item) === currentValue;
});
if (!matchingItem) {
writeValueToModel(null);
}
}
I also found that the first item in the autocomplete list was being automatically selected, but then noticed by setting autofocus: false solved my issue, e.g.,
<input type="text" data-bind="
jqAuto: { autoFocus: false }, /* This fixes the auto select issue */
jqAutoSource: surnames,
jqAutoQuery: surnameAutocomplete,
jqAutoValue: surname,
jqAutoForceSelection: false"
/>
If you look closely at the binding handler you're using, you will notice this section:
//on a change, make sure that it is a valid value or clear out the model value
options.change = function(event, ui) {
var currentValue = $(element).val();
var matchingItem = ko.utils.arrayFirst(unwrap(source), function(item) {
return unwrap(item[inputValueProp]) === currentValue;
});
if (!matchingItem) {
writeValueToModel(null);
}
What this section of the binding handler essentially does is check if the text the user entered into the text field matches something in the autocomplete dropdown, and if it doesn't, it clears the model value (which it sounds like what you want to change).
You can either try deleting this section or extend it to suit your purposes.
I have a situation involving KnockoutJS & CKEditor.
Basically we've got part of our site that is 'single page' app style, currently it just involves 2 pages but will likely expand over time, currently it's just a 'listings' page and a 'manage' page for the items in the list.
The manage page itself requires some sort of rich text editor, we've gone with CKEditor for a company wide solution.
Because these 2 pages are 'single page' style obviously CKEditor can't register against the manage elements because they aren't there on page load - simple enough problem to fix. So as a sample I attached CKEditor on a click event which worked great. The next problem was that then the Knockout observables that had been setup weren't getting updated because CKEditor doesn't actually modify the textarea it's attached too it creates all these div's/html elements that you actually edit.
After a bit of googleing I found an example of someone doing this with TinyMCE - http://jsfiddle.net/rniemeyer/GwkRQ/ so I thought I could adapt something similar to this for CKEditor.
Currently I'm quite close to having a working solution, I've got it initialising and updating the correct observables using this technique (I'll post code at the bottom) and even posting back to the server correctly - fantastic.
The problem I'm currently experiencing is with the 'Single Page' app part and the reinitialisation of CKEditor.
Basically what happens is you can click from list to manage then save (which goes back to the list page) then when you go to another 'manage' the CKEditor is initialised but it doesn't have any values in it, I've checked the update code (below) and 'value' definitely has the correct value but it's not getting pushed through to the CKEditor itself.
Perhaps it's a lack of understanding about the flow/initialisation process for CKEditor or a lack of understanding about knockout bindings or perhaps it's a problem with the framework that's been setup for our single page app - I'm not sure.
Here is the code:
//Test one for ckeditor
ko.bindingHandlers.ckeditor = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
var options = allBindingsAccessor().ckeditorOptions || {};
var modelValue = valueAccessor();
$(element).ckeditor();
var editor = $(element).ckeditorGet();
//handle edits made in the editor
editor.on('blur', function (e) {
var self = this;
if (ko.isWriteableObservable(self)) {
self($(e.listenerData).val());
}
}, modelValue, element);
//handle destroying an editor (based on what jQuery plugin does)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
var existingEditor = CKEDITOR.instances[element.name];
existingEditor.destroy(true);
});
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
//handle programmatic updates to the observable
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).html(value);
}
};
So in the HTML it's a fairly standard knockout 'data-bind: ckeditor' that applyies the bindings for it when the ViewModel is initialised.
I've put debugger; in the code to see the flow, it looks like when I load the first time it calls init, then update, when I go in the second time it hits the ko.utils.domNodeDisposal to dispose of the elements.
I've tried not destroying it which CKEditor then complains that something already exists with that name. I've tried not destroying it and checking for if it exists and initialising if it doesn't - that works the first time but the second time we have no CKEditor.
I figure there's just one thing I'm missing that will make it work but I've exhausted all options.
Does anyone have any knowledge on integrating these 3 things that can help me out?
Are there any knockout experts out there that might be able to help me out?
Any help would be much appreciated.
MD
For anyone interested I sorted it:
All it was was a basic order of execution, I just needed to set the value to the textarea html before it got initialised.
Note this uses a jquery adaptor extension to do the .ckeditor() on the element.
There is probably also a better way to do the 'blur' part.
This extension also doesn't work with options at the moment but that should be quite simple in comparison.
ko.bindingHandlers.ckeditor = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
var options = allBindingsAccessor().ckeditorOptions || {};
var modelValue = valueAccessor();
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).html(value);
$(element).ckeditor();
var editor = $(element).ckeditorGet();
//handle edits made in the editor
editor.on('blur', function (e) {
var self = this;
if (ko.isWriteableObservable(self)) {
self($(e.listenerData).val());
}
}, modelValue, element);
}
};
I've been working with this for a while now and ran again several problems with the .on("blur") approach. Namely, when people clicked in to the rich text and entered text then scrolled directly to the Save button on my form, the observable didn't get updated fast enough. There are a ton of ways to handle delays, but I wanted something more official. I dug in to the CKEditor documentation and found this gem: focusManager
This is built-in functionality that handles all the instances of focus and blur and allows you to hook up a true blur event to the control.
Here is my bindingHandler for rich text then
ko.bindingHandlers.richText = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var txtBoxID = $(element).attr("id");
var instance = CKEDITOR.instances[txtBoxID];
var options = allBindingsAccessor().richTextOptions || {};
options.toolbar_Full = [
['Source', '-', 'Format', 'Font', 'FontSize', 'TextColor', 'BGColor', '-', 'Bold', 'Italic', 'Underline', 'SpellChecker'],
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl'],
['Link', 'Unlink', 'Image', 'Table']
];
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
if (CKEDITOR.instances[txtBoxID]) { CKEDITOR.remove(CKEDITOR.instances[txtBoxID]); };
});
$(element).ckeditor(options);
//wire up the blur event to ensure our observable is properly updated
CKEDITOR.instances[txtBoxID].focusManager.blur = function () {
var observable = valueAccessor();
observable($(element).val());
};
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var val = ko.utils.unwrapObservable(valueAccessor());
$(element).val(val);
}
}
Building up on the work done in the other answers here's my solution:
handles changes using ckeditor's own change event (updates on keypress but not just that)
uses ckeditor's getData() so you don't get unwanted HTML like "magic line" and similar stuff
handles memory management (untested)
Code:
ko.bindingHandlers.ckeditor = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var options = allBindingsAccessor().ckeditorOptions || {};
var modelValue = valueAccessor();
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).html(value);
$(element).ckeditor();
var editor = $(element).ckeditorGet();
//handle edits made in the editor
editor.on('change', function(e) {
var self = this;
if (ko.isWriteableObservable(self)) {
self($(e.listenerData).val());
}
}, modelValue, element);
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
if (editor) {
CKEDITOR.remove(editor);
};
});
},
update: function(element, valueAccessor, allBindingsAccessor, context) {
// handle programmatic updates to the observable
var newValue = ko.utils.unwrapObservable(valueAccessor());
if ($(element).ckeditorGet().getData() != newValue)
$(element).ckeditorGet().setData(newValue)
}
};
The markup I use (note the afterkeydown):
<textarea
id="editor1"
data-bind="ckeditor: text, valueUpdate: 'afterkeydown'"
></textarea>
Update: as requested in the comments, here is a minimal working Fiddle.
First post so let me know if I've done anything wrong
In my project, I gave visual feedback as to whether there were unsaved changes and so needed the observable updated on keyup. And on click for when a toolbar button was clicked. This also was consistent with me using valueUpdate:['afterkeydown','propertychange','input'] in my data-bind attributes.
Also, for performance, I used the callback method parameter of .ckeditor(callback,options) rather than .on(eventName,handler).
This is the custom binding I came up with:
ko.bindingHandlers.ckeditor = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
// get observable
var modelValue = valueAccessor();;
$(element).ckeditor(function(textarea) {
// <span> element that contains the CKEditor markup
var $ckeContainer = $(this.container.$);
// <body> element within the iframe (<html> is contentEditable)
var $editorBody =
$ckeContainer.find('iframe').contents().find('body');
// sets the initial value
$editorBody.html( modelValue() );
// handle edits made in the editor - by typing
$editorBody.keyup(function() {
modelValue( $(this).html() );
});
// handle edits made in the editor - by clicking in the toolbar
$ckeContainer.find('table.cke_editor').click(function() {
modelValue( $editorBody.html() );
});
});
// when ko disposes of <textarea>, destory the ckeditor instance
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).ckeditorGet().destroy(true);
});
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
// handle programmatic updates to the observable
var newValue = ko.utils.unwrapObservable(valueAccessor());
var $ckeContainer = $(element).ckeditorGet().container;
if( $ckeContainer ) {
// <span> element that contains the CKEditor markup
$ckeContainer = $($ckeContainer.$);
// <body> element within the iframe (<html> is contentEditable)
var $editorBody =
$ckeContainer.find('iframe').contents().find('body');
// if new value != existing value, replace it in the editor
if( $editorBody.html() != newValue )
$editorBody.html( newValue );
}
}
};
Justification:
I know I should probably use .getData() and .setData(html) instead of this rather hacky way of finding <body> and <table class="cke_editor"> within the iframe contents.
Reason being, for update: the condition within:
if( $(element).ckeditorGet().getData() != newValue )
$(element).ckeditorGet().setData( newValue )
was initially true due to HTML formatting that CKEditor does. And so, notified the user about a dirty record even though it wasn't. Very specific to me so I thought you should know, in case you were wondering why.
I just used this technique with CKEditor 4 to overwrite the existing (1-way) "html" binding with a 2-way binding. I'm using the inline CKEditor which may behave differently (not sure) than the full/static editor. I started with the "value" binding and tweaked it to work with the innerHTML instead:
ko.bindingHandlers.html = {
'init': function (element, valueAccessor, allBindingsAccessor) {
var eventsToCatch = ["blur"];
var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
var valueUpdateHandler = null;
if (requestedEventsToCatch) {
if (typeof requestedEventsToCatch == "string")
requestedEventsToCatch = [requestedEventsToCatch];
ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
}
valueUpdateHandler = function () {
var modelValue = valueAccessor();
var oldValue = ko.utils.unwrapObservable(modelValue);
var elementValue = element.innerHTML;
var valueHasChanged = (oldValue !== elementValue);
if (valueHasChanged)
modelValue(elementValue);
}
ko.utils.arrayForEach(eventsToCatch, function (eventName) {
var handler = valueUpdateHandler;
if (eventName.indexOf("after") == 0) {
handler = function () {
setTimeout(valueUpdateHandler, 0)
};
eventName = eventName.substring("after".length);
}
ko.utils.registerEventHandler(element, eventName, handler);
});
},
'update': function (element, valueAccessor) {
var newValue = ko.utils.unwrapObservable(valueAccessor());
var elementValue = element.innerHTML;
var valueHasChanged = (newValue !== elementValue);
if (valueHasChanged)
element.innerHTML = newValue;
}
};
Caveat: This should probably be updated to use CKEditor's own change event.
I rewrote this to update the observable on each keyup, instead of on blur. This is a lot more updates to the observable, but as long as you are saving with a button, this seems to work great so far!
//handle edits made in the editor
CKEDITOR.instances.thread_message.on('contentDom', function() {
CKEDITOR.instances.thread_message.document.on('keyup', function(e) {
var self = this;
if (ko.isWriteableObservable(self)) {
var ckValue = CKEDITOR.instances.element_id.getData();
self(ckValue);
//console.log("value: " + ckValue);
}
}, modelValue, element);
});
For the 'blur' part, I tried the code below and it seems to work
editor.on('blur', function (e) {
var self = this;
if (ko.isWriteableObservable(self)) {
var ckValue = e.editor.getData();
self(ckValue);
}
}, modelValue, element);
I think the 'Update' part is still needed if you "update" the observable from somewhere else (not through editing as this is taken care of by the 'blur')
I am trying to extend the default jQuery UI 1.8.16 autocomplete plugin to do a few things:
Check an array of values and not just the label/value when searching for a match
Render the menu item in a different pattern based on a category on the item
So to do this I began to work through the code. I am very new to writing a widget/plugin and so I am wondering if someone can help me understand this part. I am currently trying to do the following:
$.widget("custom.advautocomplete", $.ui.autocomplete, {
filter: function (array, term) {
var matcher = new RegExp($.ui.autocomplete.escapeRegex(term), "i");
return $.grep(array, function (value) {
return matcher.test(value.label || value.alldata || value);
});
},
_renderMenu: function (ul, items) {
var self = this,
currentCat = "";
$.each(items, function (index, item) {
if (currentCat != item.category) {
ul.append("<li class='ui-autocomplete-category'>" + item.category + "</li>");
currentCat = item.category;
}
self._renderItem(ul, item);
});
},
_renderItem: function (ul, item) {
return $("<li></li>")
.data("item.autocomplete", item)
.append($("<a></a>").text(item.alldata))
.appendTo(ul);
}
});
The _renderMenu code is pretty much taken directly from the example in the documentation. The _renderItem code is working in this example as well. What is not working is the filter code. I did notice that the filter code is defined in the jQuery library like so:
$.extend($.ui.autocomplete, {
escapeRegex: function (value) {
return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
},
filter: function (array, term) {
var matcher = new RegExp($.ui.autocomplete.escapeRegex(term), "i");
return $.grep(array, function (value) {
return matcher.test(value.label || value.value || value);
});
}
});
From what I've gathered so far, this is extending the jQuery autocomplete which is defined earlier by a call to $.widget("ui.autocomplete"... But why is this handled this way? Why are these two functions not handled inside the widget definition like everything else? I noticed that if I extend ui.autocomplete in the same manner as it is done by the base widget, then I can get the filter override to work. I would like to extend the control and include the new filter at the same time so I can keep my code a bit cleaner but I don't understand why it is being done like this. Is there a benefit that I'm missing?