I have the TinyMCE WYSiWYG Editor presenting text depending on a selected object, but experience problem with the binding.
The first "instanciation" seems to work, but when choosing a new text from the drop down list of available text the editor goes blank and Firebug console tells me:
TypeError: D.hasChildNodes is not a function
...ute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r...
and
NS_ERROR_UNEXPECTED: Unexpected error
.../,"$1"));return false}});if(!u.getParam("accessibility_focus")){g.add(i.add(k,"a...
I have tried to recreate my code here: http://jsfiddle.net/xc4sz/1/
It´s not 100% but at least it does´t work. ;)
If I instead of clicking directly from text 1 to text 2 go via the "Choose option" the text is presented properly.
I guess it has to do with the "init" section below:
ko.bindingHandlers.tinymce = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
var options = allBindingsAccessor().tinymceOptions || {};
var modelValue = valueAccessor();
var value = ko.utils.unwrapObservable(valueAccessor());
var el = $(element)
//handle edits made in the editor. Updates after an undo point is reached.
options.setup = function (ed) {
console.log(ed)
ed.onChange.add(function (ed, l) {
if (ko.isWriteableObservable(modelValue)) {
modelValue(l.content);
}
});
};
//handle destroying an editor
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
setTimeout(function () { $(element).tinymce().remove() }, 0)
});
//$(element).tinymce(options);
setTimeout(function () { $(element).tinymce(options); }, 0);
el.html(value);
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
var $element = $(element),
value = ko.utils.unwrapObservable(valueAccessor()),
id = $element.attr('id');
//handle programmatic updates to the observable
// also makes sure it doesn't update it if it's the same.
// otherwise, it will reload the instance, causing the cursor to jump.
if (id !== undefined) {
var tinymceInstance = tinyMCE.get(id);
if (!tinymceInstance)
return;
var content = tinymceInstance.getContent({ format: 'raw' });
if (content !== value) {
$element.val(value);
//this should be more proper but ctr+c, ctr+v is broken, above need fixing
//tinymceInstance.setContent(value,{ format: 'raw' })
}
}
}
};
Depending on the versions of TinyMCE and jQuery that you are dependent on, you might like to try the custom binding I've recently rolled myself.
It's available on GitHub and NuGet
I found the issue. What happened was this:
you select Textbatch #1 and make some changes
you switch to Textbatch #2
the binding changes correctly from Textbatch #1 to #2
THEN the ed.onChange.add event handler kicks in and overwrites the content of the previous Textbatch #1 with that of the new Textbatch #2
Take a look at this updated fiddle (remove /show/light from the URL to get back to the editor). I had to inline select2.js, because Github does not allow files it hosts to be included remotely, causing your fiddle to fail.
The important part is in ko.utils.domNodeDisposal.addDisposeCallback:
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).tinymce().onChange.remove(changeHandler);
setTimeout(function () { $(element).tinymce().remove() }, 0)
});
I do not know why the removal of the editor is minimally delayed with a 0-second timeout, but there is probably a good reason for that. So all we do is remove the 'change' handler, so that the old editor cannot update the bound valueAccessor in your viewmodel anymore.
EDIT: I just noticed that I fixed your fiddle, but not necessarily your original exception… here is to hoping that the two were related.
Related
I am using $.observable(array).insert() to append items to a list. This is updating my view as it should: new list items are rendered to the DOM. However, I would like to issue a click event on the new DOM node (I'm relying on the event to add a class to expand the item and attach another listener to the body so the area can be closed).
I have tried both
$.observable(_model.leadTimes).insert(leadTime);
$leadTimes.find('.lead-time-data').last().find('.start-editing').click();
...and
function watchLeadTimes() {
var changeHandler = function (ev, eventArgs) {
if (eventArgs.change === 'insert') {
$leadTimes.find('.lead-time-data').last().find('.start-editing').click();
}
};
$.observe(_model.leadTimes, changeHandler);
}
And neither of them worked, however, if I wrap the jQuery method in a setTimout, like setTimeout(function () { $leadTimes.find('.lead-time-data').last().find('.start-editing').click(); }, 400);, it does work, leading me to believe this is an issue of timing with the DOM render somehow not finishing before my jQuery click() method is invoked.
Since the odds are decent that you will see this, Borris, thank you for the library and all that you do! I think jsViews is an excellent middle ground between the monolithic frameworks out there and plain old jQuery noodling!
Edit 02/09/17
It turns out my issue was overlapping click events--I was inadvertently handling a click to deselect my element immediately after it was selected. However I took the opportunity to rewrite things to use a more declarative approach following Borris' linked example.
Now in my template I am using a computed observable, isSelected to toggle the .editing class:
{^{for leadTimes}}
<tr class="lead-time-data" data-link="class{merge:~isSelected() toggle='editing'}">
<span>{^{:daysLead}}</span>
</tr>
{{/for}}
And this JS:
function addNewLeadTimeClickHandler() {
var onNewLeadTimeClick = function () {
e.stopPropagation(); // this is what I was missing
var leadTime = {
daysLead: 1,
description: ''
};
$.observable(_model.activityMapping.leadTimes).insert(leadTime);
selectLeadtime(_model.activityMapping.leadTimes.length -1);
}
$leadTimes.on('click', '.add', onNewLeadTimeClick);
}
function selectLeadtime(index) {
var addStopEditingClickHandler = function () {
var onClickHandler = function (event) {
if ($(event.target).closest('tr').hasClass('editing')) {
setHandler();
return;
}
selectLeadtime(-1)
};
function setHandler() {
var clickEvent = 'click.ActivityChangeRequestDetailController-outside-edit-row';
$('html:not(.edit)').off(clickEvent).one(clickEvent, onClickHandler);
};
setHandler();
}
if (_model.selectedLeadtimeIndex !== index) {
$.observable(_model).setProperty('selectedLeadtimeIndex', index)
addStopEditingClickHandler();
}
}
function isSelected() {
var view = this;
return this.index === _model.selectedLeadtimeIndex;
}
// isSelected.depends = ["_model^selectedLeadtimeIndex"];
// for some reason I could not get the above .depends syntax to work
// ...or "_model.selectedLeadtimeIndex" or "_model.selectedLeadtimeIndex"
// but this worked ...
isSelected.depends = function() {return [_model, "selectedLeadtimeIndex"]};
The observable insert() method is synchronous. If your list items are rendered simply using {^{for}}, then that is also synchronous, so you should not need to use setTimeout, or a callback. (There are such callbacks available, but you should not need them for this scenario.)
See for example http://www.jsviews.com/#samples/editable/tags (code here):
$.observable(movies).insert({...});
// Set selection on the added item
app.select($.view(".movies tr:last").index);
The selection is getting added, synchronously, on the newly inserted item.
Do you have other asynchronous code somewhere in your rendering?
BTW generally you don't need to add new click handlers to added elements, if you use the delegate pattern. For example, in the same sample, a click handler to remove a movie is added initially to the container "#movieList" with a delegate selector ".removeMovie" (See code). That will work even for movies added later.
The same scenario works using {{on}} See http://www.jsviews.com/#link-events: "The selector argument can target elements that are added later"
I am totally new to knock-out custom binding, I am trying to integrate ckeditor with knock-out biding, I have the following binding got from Google search,
ko.bindingHandlers.wysiwyg = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
var allBindings = allBindingsAccessor();
var $element = $(element);
$element.attr('contenteditable', true);
if (ko.isObservable(value)) {
var isSubscriberChange = false;
var isEditorChange = true;
$element.html(value());
var isEditorChange = false;
$element.on('input, change, keyup, mouseup', function () {
if (!isSubscriberChange) {
isEditorChange = true;
value($element.html());
isEditorChange = false;
}
});
value.subscribe(function (newValue) {
if (!isEditorChange) {
isSubscriberChange = true;
$element.html(newValue);
isSubscriberChange = false;
}
});
}
}
}
I have the following code to bind,
$(function () {
$.getJSON("/getdata", function (data) {
ko.applyBindings({
testList: [{
test: ko.observable()
},
{
test: ko.observable()
}]
}, document.getElementById('htmled'));
});
});
HTML as follows
<div id="htmled" data-bind="foreach:testList">
Data
<div class="editor" data-bind="wysiwyg: test">Edit this data</div>
</div>
The binding works and show the toolbar when I call the ko.applyBindings outside the $.getJSON method. But when I call applyBindings inside, the toolbars not appearing. Can any body help me on this? I must be missing something for sure, any help on this is greatly appreciated.
Jsfiddle Added
Working :http://jsfiddle.net/jogejyothish/h4Lt3/1/
Not Working : http://jsfiddle.net/jogejyothish/Se8yR/2/
Jyothish
What's happening is this:
Your page loads with the single div. KO has yet to be applied to this div.
document.ready() fires. The CKEditor script applied CKEditor to any matching divs (none).
You make your ajax call.
The Ajax call completes. You apply bindings.
KO inserts two new divs, neither of which has CKEditor.
In order to fix it, you need to add some code inside your ajax success function to manually initialise the CKEditors, like:
$(".editor").each(function(idx, el) {
CKEDITOR.inline(el)
});
Here it is, working in your fiddle:
http://jsfiddle.net/Se8yR/5/
The reason your working version works is because the bindings are applied in document.ready, so KO renders the two div elements in time, and the CKEditor is successfully applied to them.
CKEditor takes some time to load.
In your first example, it loads after ko applies, which works fine.
In the second example, it loads before ko applies. The problem is that CKEditor looks for the contenteditable attribute which you set with ko, so the editor is not created.
You can create it manually with:
CKEDITOR.inline(element).setData(valueUnwrapped || $element.html());
Doc
Demo
I've set up a jsFiddle to demonstrate my problem.
What I'm doing:
I'm using Knockout with a jQuery Datepicker as shown in this question.
My model contains an observableArray which contains objects with date properties. Knockout renders an <input type="text"> for every object, and binds the value to the date using RP Niemeyer's datepicker binding.
What's going wrong:
If the user has a datepicker open when knockout's model changes (e.g., adding a new item), the datepicker stops working correctly. From what I can tell, the last <input type="text"> created while the datepicker is open becomes the new datepicker target. I need to fix the binding so that this does not happen.
Sample HTML:
<ul data-bind="foreach: dateBoxes">
<li>
<span data-bind="text: index"></span>
<input type="text" data-bind="datepicker: date"/>
</li>
</ul>
Sample Javascript:
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {};
$(element).datepicker(options);
//handle the field changing
ko.utils.registerEventHandler(element, "change", function() {
var observable = valueAccessor();
observable($(element).datepicker("getDate"));
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).datepicker("destroy");
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
//handle date data coming via json from Microsoft
if (String(value).indexOf('/Date(') == 0) {
value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
}
var current = $(element).datepicker("getDate");
if (value - current !== 0) {
$(element).datepicker("setDate", value);
}
}
};
var model;
var id = 0;
function DateBox(d) {
var self = this;
self.date = ko.observable(d);
self.index = ko.observable(id++);
}
function Model() {
var self = this;
self.dateBoxes = ko.observableArray([]);
self.changeCount = function() {
if (self.dateBoxes().length < 2) {
self.dateBoxes.push(new DateBox(new Date()));
} else if (self.dateBoxes().length > 1) {
self.dateBoxes.splice(0,1);
}
}
self.tick = function() {
self.changeCount();
setTimeout(function() {
self.tick();
}, 5000);
}
self.tick();
}
model = new Model();
ko.applyBindings(model);
Note: This answer isn't complete. However, as it's too large for a comment and probably helpful (towards a solution) anyhow I've taken the liberty and posted it as an answer. Anyone that can complete this answer is invited to do so through an edit or by spinning off into another better answer.
The thing that seems to be spoiling the party (taken from the documentation, emphasis mine):
This will be called once when the binding is first applied to an element, and again whenever the associated observable changes value.
If I hackisly prevent the update from doing its datepicker calls in the first update the datepicker won't break anymore (other issues do arise though). For the init I've added this line at the end:
element.isFirstRun = true;
Then the update method will do the following just before the datepicker calls:
if (element.isFirstRun) {
$(element).val(value);
element.IsFirstRun = false;
return;
}
See this updated fiddle for the results, which are:
the mentioned scenario now updates the correct textbox (a good thing);
the initial value is now a more verbose DateTime string and stays that way after updates (kinda not nice);
Hopefully this will help towards a more complete solution.
I currently use Jquery Validation and Qtip together to deal with the actual validation and displaying of information to the screen using the nice tooltip style notifications upon validation errors using the errorPlacement component of the validation options.
Currently each viewModel has its own custom method for setting up and kicking off the validation and callbacks, however I was trying to look at a nicer way of doing this, be it adding a custom binding to setup my validation rules via the data-bindings or an alternative way, but still yielding the same results (i.e the errorPlacement is triggered when a validation error occurs and tells Qtip to display the error for the given element).
Now before I started making one myself I just checked online and found Knockout Validation, which I initially thought was a great idea, I could apply my validation logic directly to the data within my viewModel and then just find some sort of callback to get Qtip to kick in, however it seems there is no callback that I can find documented. The library seems to do everything I want for the validation side of things, just not for the displaying side of things. I looked through the source code and examples but couldn't see anything other than ko.validation.group(viewModel) which would give me an observable containing the errors, but I am not sure if I could use this the same way as I was expecting.
Here is an example of how my current validation happens:
/*globals $ ko */
function SomeViewModel() {
this.SetupValidation = function () {
var formValidationOptions = {
submitHandler: self.DoSomethingWhenValid,
success: $.noop,
errorPlacement: function (error, element) {
if (!error.is(':empty'))
{ qtip.DoSomethingToDisplayValidationErrorForElement(element, error); }
else
{ qtip.DoSomethingToHideValidationErrorForElement(element); }
}
};
$(someForm).validate(formValidationOptions);
this.SetupValidationRules();
};
this.SetupValidationRules = function() {
$(someFormElement1).rules("add", { required: true, minlength: 6, maxlength: 20, alphaNumeric: true });
$(someFormElement2).rules("add", { required: true, minlength: 6, maxlength: 20 });
$(someFormElement3).rules("add", { required: true, email: true, });
};
}
I currently am sure I can remove the need for the validation rules method by adding a custom binding so I can set the validation in the data-bind, however if possible I would like to use the same sort of callback approach with the existing Knockout-Validation binding.
I haven't used Knockout-Validation specifically but I have written something similar in the past. A quick glance at the source shows that each extended observable gets a sub-observable isValid. This could be used to hide show messages in your markup using conventional knockout visible bindings.
To get QTip to work a custom binding could subscribe to this isValid property and perform the necessary initialization to show/hide QTip when triggered.
EDIT
Here is an example to get you started
http://jsfiddle.net/madcapnmckay/hfcj7/
HTML:
<!-- Note that you have to reference the "qtipValMessage" binding -->
<!-- using the "value" binding alone is not enough -->
<input data-bind="value: emailAddress, qtipValMessage : emailAddress" />
JS:
ko.bindingHandlers.qtipValMessage = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor(), $element = $(element);
if (observable.isValid) {
observable.isValid.subscribe(function(valid) {
if (!valid) {
$element.qtip({
overwrite: true,
content: {
text: observable.error
}
});
} else {
$element.qtip("destroy");
}
});
}
}
};
I had been editing madcapnmckay's post, but the differences have become significant enough that I think a new answer is needed.
It is heavily based off of madcapnmckay's post, but it fixes a bug pointed out by MorganTiley. The original only works if the user has modified the observable. If they haven't then the code never gets fired. So, I've modified it so that it fires the tooltip code when it gets created, in addition to when it changes.
ko.bindingHandlers.qtipValMessage = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor(), $element = $(element);
if (observable.isValid) {
var updateTooltip = function (valid) {
if (!valid) {
$element.qtip({
overwrite: true,
content: {
text: observable.error
}
});
} else {
$element.qtip("destroy");
}
}
updateTooltip();
observable.isValid.subscribe(updateTooltip);
}
}
};
The one downside is that the tooltip will display on hover before knockout validation has been run (example, you have a "required" validation on a field, before you press submit a tooltip will display saying the field is required, but the field will not highlight in pink). Once you change the field however, the tooltip will disappear if the field is valid.
My app was not using qtip, but rather Twitter Bootstrap Tooltip, so here is the code for that as well.
ko.bindingHandlers.invalidTooltip = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor(), $element = $(element);
if (observable.isValid) {
var updateTooltip = function (valid) {
if (!valid) {
$element.attr("data-original-title", observable.error);
$element.tooltip();
} else {
$element.tooltip("destroy");
}
}
updateTooltip();
observable.isValid.subscribe(updateTooltip);
}
}
};
I was wondering if it's possible to do as follows:
In my site I am using a lot of jQuery plugins that fire different events that I don't know about.
Is there a way - a program, a browser add-on, or something else - that I can browse the site and get a list of the exact javascript events that were fired with every click?
For example, I have a jQuery plugin that when I right click on any element a custom contextMenu shows and then when I click on one of the options other things come up. I need to know exactly what Javascript basic events were fired:
$('input:submit, button:submit').rightClick(function (e) {
$(this).contextMenu('contextMenuInput', {
'Capture This': {
click: function (element) { // element is the jquery obj clicked on when context menu launched
doSomething();
},
klass: "kgo" // a custom css class for this menu item (usable for styling)
},
'Create List': {
click: function (element) {
},
klass: "kfilter kdisabled"
},
'Collect Data': {
click: function (element) {
},
klass: "kcapture kdisabled"
}
},
{ disable_native_context_menu: true }
);
});
Does anyone have any idea?
You can use the following code to show events currently bound ....
here is an example of using this code : http://jsfiddle.net/manseuk/CNjs3/
(function($) {
$.eventReport = function(selector, root) {
var s = [];
$(selector || '*', root).andSelf().each(function() {
var e = $.data(this, 'events');
if(!e) return;
s.push(this.tagName);
if(this.id) s.push('#', this.id);
if(this.className) s.push('.', this.className);
for(var p in e) s.push('\n', p);
s.push('\n\n');
});
return s.join('');
}
$.fn.eventReport = function(selector) {
return $.eventReport(selector, this);
}
})(jQuery);
Use it like this ->
// all events
alert($.eventReport());
// just events on inputs
alert($.eventReport('input'));
// just events assigned to this element
alert($.eventReport('#myelement'));
// events assigned to inputs in this element
alert($.eventReport('input', '#myelement'));
alert($('#myelement').eventReport('input')); // same result
// just events assigned to this element's children
alert($('#myelement').eventReport());
alert($.eventReport('*', '#myelement'); // same result
Updated as per comments
If you want to see what is bound to these events this is an excellent tool -> http://www.sprymedia.co.uk/article/Visual+Event
It's not quite what your looking for, but with firebug, you can log events for a given DOM element.
You can do this by right clicking on the element in the html tab and clicking log events:
The event log:
You may also find the firebug extension "EventBug" useful:
http://getfirebug.com/wiki/index.php/Firebug_Extensions#Eventbug
http://www.softwareishard.com/blog/firebug/eventbug-alpha-released/