i have an application built with knockout that utilises a wysiwyg called redactor (not that I think that is relevant to this problem but just in case)
I have just run into an issue where a user has an iPad and when they save a 'note' it is missing the last letter each time - nobody else has ever had this issue.
It is evident that this is down to the underlying text area not updating on the last key press, but I am not sure how to fix this.
I have a custom binding as follows
ko.bindingHandlers.redactor = {
init: function(element, valueAccessor) {
var value = valueAccessor();
if (ko.isObservable(value)) {
$(element).redactor({
changeCallback: value,
fileUpload: 'url',
fileManagerJson: site_URL + 'files/files.json',
plugins: ['filemanager', 'clips', 'textexpander', 'bufferbuttons'],
textexpander: [
["##s", "<strong>(S)</strong> - "]
]
});
}
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()) || '';
if (value !== $(element).redactor('core.getTextarea').val()) {
$(element).redactor('code.set', value );
}
}
}
How can i change this to make sure it behaves as expected on the iPad and updates on the final key press? Or is there a way of simply forcing the update on keyPress / keyDown within an update.
The code samples in the documentation retrieve the widget value like this:
$('#redactor').redactor({
callbacks: {
change: function()
{
console.log(this.code.get());
}
}
});
In other words, they call this.code.get() in the callback body.
Your setup on the other hand...
$(element).redactor({
changeCallback: value
});
it implicitly uses the first argument to the callback as the value.
Does it make a difference if you use the same approach the documentation suggests?
$(element).redactor({
changeCallback: function () {
value(this.code.get());
});
});
Related
I am trying to load data from a websocket to show on a gauge using knockout and gaugeMeter.js.
I keep getting the "cannot apply bindings multiple times to the same element" error.
Here is the code
HTML
<div class="GaugeMeter gaugeMeter" id="PreviewGaugeMeter" data-bind="gaugeValue: Percent" data-size=350 data-theme="Orange" data-back="Grey"
data-text_size="0.4" data-width=38 data-style="Arch" data-animationstep="0" data-stripe="3"></div>
JS
// Bind new handler to init and update gauges.
ko.bindingHandlers.gaugeValue = {
init: function(element, valueAccessor) {
$(element).gaugeMeter({ percent: ko.unwrap(valueAccessor()) });
},
update: function(element, valueAccessor) {
$(element).gaugeMeter({ percent: activePlayerData.boost });
}
};
// Create view model with inital gauge value 15mph
// Use observable for easy update.
var myViewModel = {
Percent: ko.observable(15)
};
ko.applyBindings(myViewModel);
The activePlayerData.boost is the data I am getting from the websocket and need to update the value, it always shows the first value but everything after that is giving the error.
I am really lost with the knockout stuff as I am very new to coding.
A minimal, working sample for your use case is below. You can run it to see what it does:
// simple gaugeMeter jQuery plugin mockup
$.fn.gaugeMeter = function (params) {
return this.css({width: params.percent + '%'}).text(params.percent);
}
// binding handler for that plugin
ko.bindingHandlers.gaugeValue = {
update: function(element, valueAccessor) {
var boundValue = valueAccessor();
var val = ko.unwrap(boundValue); // or: val = boundValue();
$(element).gaugeMeter({ percent: val });
}
};
// set up viewmodel
var myViewModel = {
Percent: ko.observable(15)
};
ko.applyBindings(myViewModel);
// simulate regular value updates
setInterval(function () {
var newPercentValue = Math.ceil(Math.random() * 100);
myViewModel.Percent(newPercentValue);
}, 1500);
div.gaugeMeter {
background-color: green;
height: 1em;
color: white;
padding: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="gaugeMeter" data-bind="gaugeValue: Percent"></div>
Things to understand:
The update part of the binding handler is called whenever the bound observable (i.e. Percent) changes its value. You don't strictly need the init part if there is nothing to initialize.
The valueAccessor gives you the thing that holds the bound value. Typically, but not necessarily this is an observable - like in your case. Once you have called valueAccessor(), you still need to open (aka "unwrap") that observable to see what's inside. This is why my code above takes two steps, valueAccessor() and ko.unwrap().
When would it ever not be an observable? - You are free to bind to literal values in your view (data-bind="gaugeValue: 15"), or to non-observable viewmodel properties (data-bind="gaugeValue: basicEfficiency") when the viewmodel has {basicEfficiency: 15}. In all cases, valueAccessor() will give you that value, observable or not.
Why use ko.unwrap(boundValue) instead of boundValue()? - The former works for both literal values and for observables, the latter only works for observables. In a binding handler it makes sense to support both use cases.
Once an update arrives, e.g. via WebSocket, don't try to re-apply any bindings or re-initialize anything.
All you need to do is to change the value of the respective observable in your viewmodel. Changing an observable's value works by calling it: myViewModel.Percent(15); would set myViewModel.Percent to 15.
If 15 is different from the previous value, the observable will take care of informing the necessary parties (aka "subscribers"), so all required actions (such as view updates) can happen.
I have the cordova plugin globalization and I wish to use the .dateToString to alter dateTimes that are in the JSON objects
incoming JSON = [{"alphaID":"JV033","status":"open","dateadded":1433531166},{"alphaID":"JV032","status":"on hold","dateadded":1433530583}]
so this is my code
document.addEventListener("deviceready", getJSONList, false);
var dateToDisplay = ''
function getJSONList() {
$.getJSON("https://myserver.com/ajax_calls/list_sr_app.asp", function(e) {
e.forEach(function(arrayItem, index, thearray) {
var x = arrayItem.dateadded * 1000; //alert(new Date(x)); this works (un-adjusted dates of course)
//alert(displayDate(x)); this does NOT work - delivers undefined
thearray[index].arrayItem.dateadded = displayDate(x) // the assigment of the return of displayDate(x) does not work either
});
constructJSONList(e); //this function (not here) works
});
} // end of getJSONList()
function displayDate(d) {
navigator.globalization.dateToString(
new Date(d),
function(date) {
dateToDisplay = date.value; /* alert(dateToDisplay); - this alert works*/
},
function() {
dateToDisplay = 'Error getting dateString'
}, {
formatLength: 'short',
selector: 'date and time'
}
);
return dateToDisplay;
}
There are two issues:
As soon as I try to use the date.value outside of the results function, even in a variable, it becomes "undefined", so return dateToDisplay; does not actually work. When calling the function like alert(displayDate(1433531166000)); the result in the alert box is "undefined" unless that call is from within the function (as is commented out above)
How can I replace the object's parameter "dateadded" with a new value - at the moment I can't make any changes at all.
You have added the event listener for 'device ready' but it looks as if your calls to get the json (and display the date) aren't being done before the device is actually ready. Cordova functions won't run (won't be defined) until the device is ready.
Try this event listener:
document.addEventListener( 'deviceready', onDeviceReady.bind( this ), false );
function onDeviceReady() {
// TODO: Cordova has been loaded. Perform any initialization that requires Cordova here.
getJSONList();
};
This works for me, anyway, and the debugger gives me 'undefined' for 'globalization' if I do the event listener the way you have done.
I'm trying to implement a custom binding for an accordion-like document layout on a webpage, but I'm encountering an issue I can't easily solve.
Immediately on page load, I am presented with the error:
Uncaught TypeError: Unable to process binding "accordion: function (){return currentAccordionSection }"
Message: undefined is not a function
I have tried declaring my observable as both a function and normally in the data-bind syntax without success. I have initialized my observable with a default value (null) and it has not fixed this issue. Below is my entire ViewModel:
var libraryViewModel = function () {
var self = this;
ko.bindingHandlers.accordion = {
update: function (element, valueAccessor) {
console.log(ko.unwrap(valueAccessor()));
var value = ko.unwrap(valueAccessor());
var section = $(element.text());
//ko.bindingHandlers.css.update(element, function () {
// if (value === section) {
// return 'library-section-active';
// }
//});
//ko.bindingHandlers.css.update($(element).children('i:last-child').get(0), function () {
// if (value === section) {
// return 'fa fa-chevron-up';
// } else {
// return 'fa fa-chevron-down';
// }
//});
}
}
self.currentAccordionSection = ko.observable(null);
self.updateAccordionSection = function (section) {
self.currentAccordionSection(section);
}
}
Some of the code above is commented out as it is not relevant to the problem at hand and I have disabled it to implement a reduced test case to narrow down the problem. Here is my binding declaration:
<h2 class="library-header" data-bind="accordion: currentAccordionSection, click: updateAccordionSection.bind('Text')">
What exactly am I doing wrong?
The problem is this line:
var section = $(element.text());
as per knockout's documentation
element — The DOM element involved in this binding
text is a jQuery function not a DOM function so I think you are looking for something like:
$(element).text() or $($(element).text()) instead? I'd assume the former since it makes more sense.
As for the nested binding handler I'm not sure why that is in the viewmodel since it's exposed on the knockout global object you're not protecting yourself from anything just making your code more unreadable. They are designed to be resuable so you can use them with different viewModels
I have a simple viewmodel with an observalbe array of items, and an observable holding the selected item. I subscribe to the changes of selected item, and I can see in my tests that the handler is fired even when I assign the same value again and again, so there should not be any change. The following code shows 3 alerts with all the same "changed to ..." text.
view.SelectedItem(view.Items()[0]);
view.SelectedItem.subscribe(function(newValue) {
alert("changed to " + ko.toJSON(newValue));
});
view.SelectedItem(view.Items()[0]);
view.SelectedItem(view.Items()[0]);
view.SelectedItem(view.Items()[0]);
Here is a demo fiddle.
Apparently, selecting an item, even if it's the same one as what's already selected, triggers the change event, calling the function specified when subscribing.
If you want to be notified of the value of an observable before it is about to be changed, you can subscribe to the beforeChange event. For example:
view.SelectedItem.subscribe(function(oldValue) {
alert("The previous value is " + oldValue);
}, null, "beforeChange");
Source
This could help you determine whether or not the value has changed.
You can create function to have access to old and new values for compare it:
ko.subscribable.fn.subscribeChanged = function(callback) {
var previousValue;
this.subscribe(function(oldValue) {
previousValue = oldValue;
}, undefined, 'beforeChange');
this.subscribe(function(latestValue) {
callback(latestValue, previousValue);
});
};
You could add this function to some file with you ko extensions. I once found it on stackoverflow but can't remeber link now. And then you could use it like this:
view.SelectedItem.subscribeChanged(function(newValue, oldValue) {
if (newValue.Name != oldValue.Name || newValue.Quantity != oldValue.Quantity) {
alert("changed to " + ko.toJSON(newValue));
}
});
Fiddle
I ended up creating my own method based on a thread on a forum:
// Accepts a function(oldValue, newValue) callback, and triggers it only when a real change happend
ko.subscribable.fn.onChanged = function (callback) {
if (!this.previousValueSubscription) {
this.previousValueSubscription = this.subscribe(function (_previousValue) {
this.previousValue = _previousValue;
}, this, 'beforeChange');
}
return this.subscribe(function (latestValue) {
if (this.previousValue === latestValue) return;
callback(this.previousValue, latestValue);
}, this);
};
I want to create a custom binding that behaves like the if binding, but instead of removing the element entirely, it replaces it with another element of the same height whenever it should be removed.
I'm struggling to find a way of doing this that isn't hacky. I don't know enough about the internals of knockout to go about this in an educated way.
Any pointers greatly appreciated.
Thanks!
You could write your own binding:
ko.bindingHandlers.shim = {
update: function(element, valueAccessor, allBindings) {
// First get the latest data that we're bound to
var value = valueAccessor();
// Next, whether or not the supplied model property is observable, get its current value
var shim = ko.unwrap(value);
if (shim) {
var shimEl = $(element).data('shim');
// Create the shim element if not created yet
if (!shimEl) {
shimEl = $('<div />').addClass('shim').appendTo(element);
// Equal the height of the elements
shimEl.height($(element).height());
$(element).data('shim', shimEl);
}
shimEl.show();
} else {
var shimEl = $(element).data('shim');
if (shimEl) {
shimEl.hide();
}
}
// You can also trigger the if-binding at this point
// ko.bindingHandlers.if.update(element, valueAccessor, allBindings);
}
};
Then use it like this:
<div data-bind="shim: [condition]"></div>