Kendo UI Javascript redraw of TreeView not rendering - javascript

After creating and rendering a Kendo UI TreeView to fill a DIV, repeat invocation alternately renders only "loading..." or works properly. Since I am having possibly similar problems with Kendo UI ContextMenu, I speculate there may be some required cleanup in between, which is passively done by even invocations such that odd invocations work, but I can't figure it out (a link to Kendo UI docs I might be missing so I can understand why I've missed this would be appreciated to help with other issues).
In my JSFiddle example, click "draw" over and over and you'll see the alternate behavior. Speculatively clicking "draw, destroy, draw, destroy..." does not seem to help.
https://jsfiddle.net/rk3nfnnu/
<script>
function TreeDestroy() { // http://stackoverflow.com/questions/5431351
$('#Tree_Space').data('kendoTreeView').destroy();
alert('destroyed');
}
function TreeShow() {
$('#Tree_Space').kendoTreeView({
dataSource: [ { Name: 'Top', items: [ { Name:'Item' } ] } ],
template: kendo.template($('#Tree_template').html())
});
alert('shown');
}
</script>
draw |
destroy
<div id='Tree_Space'>
</div>
<script type='text/x-kendo-template' id='Tree_template'>
#= item.Name#
</script>

I have updated that fiddle. The destroy(); method probably only destroys allocated dom elements after the widget was rendered (the nodes). I doubt it cleans up the wrappers and whatnot. In your TreeDestroy(), issue a clear on that element div. Of course, you should call TreeDestroy prior to TreeCreate just in case.
function TreeDestroy() { // http://stackoverflow.com/questions/5431351
$('#Tree_Space').data('kendoTreeView').destroy();
$('#Tree_Space').html('');
alert('destroyed');
}
Here is some kendoui documentation that refers to how to handle manual deletion of widgets.

Related

Kendo UI TreeView context-right click confuses selection

Below I've tried to simplify more complex TreeView usage, where I'm failing to implement a ContextMenu on TreeView node items, into a fiddle that exhibits potentially related issues. Steps: In my simplified example one left clicks to select a node, then right clicks on another node, and finally dismisses with escape, and then selection indications are confused. I've tried "return false", select(nothing), and preventDefault() to no avail.
My question is: is this a bug in Kendo UI, or am I missing something in my usage of TreeView?
https://jsfiddle.net/3cp9m8wo/
<div id='Tree_Space'></div>
<script type='text/x-kendo-template' id='Tree_template'>
#= item.Name#
</script>
<script>
$('#Tree_Space').kendoTreeView({
dataSource: [ { Name: 'Top', items: [ { Name:'Item' }, { Name:'Item' } ] } ],
template: kendo.template($('#Tree_template').html())
});
$("#Tree_Space").data("kendoTreeView").expand('.k-item');
</script>
My full goal would be to disable Kendo UI selection on TreeView nodes completely, allowing me to implement left clicks (actions) and right clicks (ContextMenus) for elements I placed within tree nodes. However, I've not seen a way to disable select on TreeView. I do find JQuery.click() does seem to work and deselect Kendo UI selections, but Kendo UI Context Menu fails to popup on right click, and displays other artifacts - one of which I think I've isolated here in hopes of learning something.
You could try using something in your template to control this:
<script type='text/x-kendo-template' id='Tree_template'>
//Ideally, your server will return a collection that determines if the item can be selected, likewise, you could add a IsParentNode or something to indicate the item should be treated differently.
#if (item.CanSelect != 'null' && item.CanSelect==true) { #
<span>#: item.Name#</span>
#}else{#
<span class='no-select'>#: item.Name#</span>
#}#
</script>
<script type="text/javascript">
$(document).ready(function () {
//Add code here to tie into the onmousedown of all .no-select elements
$(document).on("click", "no-select", function (e) {
e.preventDefault();
});
});
</script>

Can't find element using UI hash in Marionette Layout

I'm not sure why I can't get the button element using my UI hash. This is what my Layout looks like:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
ui: {
btnSave: "#btnSave"
},
events: {
"click #ui.btnSave": "onSave"
},
onInitialize: function () {
this.listenTo(App.vent, "DisableSaveButton", function(val) {
this.disableSaveButton(val);
},this);
},
disableSaveButton: function () {
this.ui.btnSave.prop("disabled",val).toggleClass("ui-state-disabled",val);
},
onSave: function () {
alert("saved!");
}
})
In VS2013, when my breakpoint hits the line inside disableSaveButton method, I entered $("#btnSave") into the Watch window and I was able to get the element back. I could tell because it had a length of 1. From this, I know the button is rendered. However, if I enter this.ui.btnSave into the Watch window, I would get an element with length of 0.
My BaseLayout object is basically a custom object extended from Marionette.Layout
Marionette version: 1.8.8
Any ideas why I can't find the button element using this.ui.btnSave?
Thanks in advance!
Got some help from a coworker and the issue might be because the element is out of scope. Basically, inside the Layout object, 'this' does not contain the element. We were able replace 'this.ui.btnSave' with '$("#btnSave",this.buttonset.el)' and that works fine. buttonset is the region that actually contains the html element.
This seems like an inconsistency because even though the ui hash didn't work, the click event utilizing the ui hash did work.
UPDATE 6/3/2015:
Another coworker of mine provided a better solution. Basically, in my Layout I use a display function to display my view. It looks something like this:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
display: function() {
$(this.buttonset.el).html(_.template($("#buttonset-view").html(), {"viewType": viewType}));
}
})
Basically, I'm saying to set the html of my region, which is this.buttonset.el, to my template's html. As of now, my layout doesn't know any of the elements inside the region. It just contains a region which displays the elements. So there is some sort of disconnect between my layout and the elements in my region.
The correct solution, as opposed to my earlier workaround, is to simply add the following line of code at the end:
this.bindUIElements();
From Marionette Annotated Source:
This method binds the elements specified in the “ui” hash inside the
view’s code with the associated jQuery selectors.
So this final code looks like this:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
display: function() {
$(this.buttonset.el).html(_.template($("#buttonset-view").html(), {"viewType": viewType}));
this.bindUIElements();
}
})
With this, I was able to finally able to retrieve my element using this.ui.btnSave.

Releasing OrbitControls in threejs

I have a Single Page Application with a lot of stuff in it, using durandal. On one page I have a link that leads to a different page where 3d model is rendered. OrbitControls are used to make model turn etc. That takes away my default left click and right click. After leaving that page, it still keeps mouse bindings and my left click and right click become useless for some uses like - selecting an tag meaning that input tags cannot be accessed again.
I could release bindings and reset them if I knew how. There is a deactivate function which is called when that 3d window is closed, but I have no idea what piece of code to write there. Any help would be extremely useful. I doubt any code will be of any use so I won't put any.
Thank you!
Due to request, here is simplified viewmodel:
define(['services/logger'], function (logger) {
var vm = {
attached: attached
};
return vm;
function attached(view) {
var camera, cameraTarget, scene, renderer, controls;
init();
animate();
function init() {
...
controls = new THREE.OrbitControls(camera);
...
}
function animate(){...}
function render(){...}
}
}
View is extremely complicated, but pasted here in full:
<div id="canvasDiv" style="overflow: hidden; width:100%; height:100%">
</div>
Actually, it would probably be helpful to include your code to setup OrbitControls (I'm not familiar with it).
The best approach to this whole issue would probably be to write a Knockout custom binding (google if you don't know them). A custom binding is a great place to abstract away DOM manipulation, in your case for setting up OrbitControls.
Let's assume that you have a div on which you set up OrbitControls. You could then do something like the following:
HTML:
<div data-bind="myOrbitControlsBinding: { someSetting: true; someOtherSetting: false }"></div>
JavaScript:
ko.bindingHandlers.myOrbitControlsBinding = {
init: function (element, valueAccessor) {
var settings = ko.utils.unwrapObservable(valueAccessor());
setupOrbitControlsOnElement(element, settings); // This should be your setup code for OrbitControls
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
// Here, you should unbind the event handlers for mouse clicks. How you do this depends on how OrbitControls sets them up. Please refer to their documentation for this. Maybe there is a generic dispose function?
disposeOrbitControls(element);
});
}
}
Edit:
Ah, I didn't realize you use Three. I've quickly scanned their documentation to see if they use some sort of input module that captures the events. They don't seem to. Which probably means that some where in your code, there is the keyword 'addEventListener' (search for it). This will be where the events are caught.
Your view probably has a viewmodel attached to it since you're using durandal. Inside the viewmodel, add a 'deactivate' method (and return it). In this method, you need to remove the event listener again. You probably already guessed it, but the method is called removeEventListener (see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.removeEventListener for an explanation)
I still highly recommend that you create a custom binding to setup Three. This will give you much more control over creation/deletion than you might have now. If you don't want to, make sure that Three is initialized inside the viewmodel as well, in the activate method.
Let me know if that helped, otherwise some viewmodel / three init code would be helpful.

How to add events to javascript runtime created html widgets

I'm developing a web community in CakePHP and started to have doubts about using jQuery to provide useful widgets around the views when needed.
For example, I've wrote a jQuery plugin which searches the data inside specific input text, asks to my database and get the results handled with events.
Basically the plugin is perfect for simple applications but it's basically useless for a community where I use it almost in all the views and every time handling it with different events and methods, making it with huge event declarations and very annoying to be debugged.
I thougt to solve the problem by using default events from the widget and add the possibility set additional events specific for the view, but how can i do that?
this is the situation i thought
The Green area of the image is where I'm not sure, where I should put the default events to be retrived every time i need them? After known that then, in the view, I could add some event to the widget to be more easy to use.
For widget I intend every kind of html portion is loaded via javascript and is interactive, maybe an input search which retrieves a list of results or something like that.
My question is how can I set default events in runtime to the widget without copy and paste every time?
And my second question is, how can I add to them specific events for the view only?
Some tutorial somewhere online would be also nice.
My answer requires backbone.js so I am not sure if this will help you.
You could separate your widget into a wrapper and the real widget.
The wrapper could handle events like your close event:
var WidgetWrapper = Backbone.View.extend({
tagName: 'div',
// This should be a mustache template:
template: '<a class="close" href="#">close</a><div class="content"></div>',
events: {
'.close click': 'close',
'.open click' : 'open'
},
close: {
this.$el.hide();
},
open: {
alert('I am open');
}
render: {
this.$el.html(Mustache.to_html(view.template, view.model.toJSON());
}
});
The real widget could render itself inside the wrapper widget and both views could interact with the data model (this.model).
var SpecialWidget = Backbone.View.extend({
tagName: 'div',
// This should also be a mustache template:
template: '<input> open',
events: {
'input change': 'edit'
},
render: function() {
if(!this.wrapper) {
this.wrapper = new WidgetWrapper();
}
// Hand over the model to the wrapper
this.wrapper = this.model;
// Render the wrapper
this.wrapper.render();
// Insert the widget content inside the wrapper
this.$el.empty().append(this.wrapper.$el);
this.$(".content").html(Mustache.to_html(view.template, view.model.toJSON());
},
edit: function() {
alert("Changed");
},
});
This would allow you to separate your events.
You could also do it the other way round and use a wrapper with a sub view.

ExtJS: starting HtmlEditor defaulting to source

I'm using ExtJS 3.2.1 and I need a component almost identical to the bundled HtmlEditor, with one exception: it must start editing the HTML source code directly. The reason I don't use a normal TextArea is that the user should be able to preview the result of his actions before submitting.
I've tried calling toggleSourceEdit(), as per ExtJS documentation, with no success. Debugging, I see that the editor object has the sourceEditMode property set to true, and the Source Edit button seems as if it was "pressed", but clicking on it does not render the typed HTML, and clicking it again goes to the Source Mode.
I've tried calling toggleSourceEdit() after the container show() method, on the container afterLayout listener and on the editor afterRender listener. I've tried also calling it on another button that I added to the container. The result is the same on every try.
The only other option I see is updating ExtJS to 3.3.0, but I haven't seem anything related on the changelogs. Either way, it's going to be my next step. EDIT: The app had another problems when updating, we'll make a bigger effort to update later. As of right now, we are using the HtmlEditor in its original setting.
Thanks!
ran into the same problem (using 3.3.0 by the way)
stumbled upon a fix by dumb luck. i have no idea why this works, but second time is the charm. call it twice in a row to achieve the desired effect..
HTMLEditor.toggleSourceEdit(true);
HTMLEditor.toggleSourceEdit(true);
hope that helps!
Rather calling toggleSourceEdit(), try to setup the configuration while you create HtmlEditor Object
Using toggleSourceEdit() caused some problems for me. One was that this seemed to put the editor somewhere in limbo between source edit and WYSIWYG mode unless I used a timeout of 250ms or so. It also puts the focus in that editor, and I don't want to start the form's focus in the editor, especially since it's below the fold and the browser scrolls to the focused html editor when it opens.
The only thing that worked for me was to extend Ext.form.HtmlEditor and then overwrite toggleSourceEdit, removing the focus command. Then adding a listener for toggling to the source editor when the component is initialized. This is for Ext 4.1 and up. For older versions, replace me.updateLayout() with me.doComponentLayout().
var Namespace = {
SourceEditor: Ext.define('Namespace.SourceEditor', {
extend: 'Ext.form.HtmlEditor',
alias: 'widget.sourceeditor',
initComponent: function() {
this.callParent(arguments);
},
toggleSourceEdit: function (sourceEditMode) {
var me = this,
iframe = me.iframeEl,
textarea = me.textareaEl,
hiddenCls = Ext.baseCSSPrefix + 'hidden',
btn = me.getToolbar().getComponent('sourceedit');
if (!Ext.isBoolean(sourceEditMode)) {
sourceEditMode = !me.sourceEditMode;
}
me.sourceEditMode = sourceEditMode;
if (btn.pressed !== sourceEditMode) {
btn.toggle(sourceEditMode);
}
if (sourceEditMode) {
me.disableItems(true);
me.syncValue();
iframe.addCls(hiddenCls);
textarea.removeCls(hiddenCls);
textarea.dom.removeAttribute('tabindex');
//textarea.focus();
me.inputEl = textarea;
} else {
if (me.initialized) {
me.disableItems(me.readOnly);
}
me.pushValue();
iframe.removeCls(hiddenCls);
textarea.addCls(hiddenCls);
textarea.dom.setAttribute('tabindex', -1);
me.deferFocus();
me.inputEl = iframe;
}
me.fireEvent('editmodechange', me, sourceEditMode);
me.updateLayout();
}
})
}
Then to use it:
Ext.create('Namespace.SourceEditor', {
/*regular options*/
listeners: {
initialize: function(thisEditor) {
thisEditor.toggleSourceEdit();
}
}
});
htmlEditor.toggleSourceEdit(true);
one time should be enough if you do this listening to the afterrender event of the editor.

Categories

Resources