I wanted to destroy and recreate multiselect widget from Telerik's Kendo UI. Normally it is easy thing which I done much times before, but never with multiselect. The problem I am facing now is that way which should work (atleast I think it should) ... does not.
Here is code which I am using to destroy and recreate components like grids or dropdowns:
if ($('#dropdown1').data('kendoDropDownList')) {
$('#dropdown1').data('kendoDropDownList').destroy();
$('#dropdown1').html('');
}
How i said - If I use it on dropdown or grid - it works. But on the multiselect it does not:
if ($('#multiselect1').data('kendoMultiSelect')) {
$('#multiselect1').data('kendoMultiSelect').destroy();
$('#multiselect1').html('');
}
I prepared small Dojo example where is shown the behaviour. When dropdown is destroyed and recreated it looks correct. When I do the same to Multiselect it always add widget as next line.
Of course I can overcome this problem by changing dataSource and just call read method or something like that but I whould like to know if it is bug or there is another way how to destroy multiselects.
Thanks.
This code uses unwrap() to remove the original input from the kendo wrapper div and then .remove() to get rid of leftover kendo DOM elements:
$('html').on('click', '#destroy2', function(e){
if ($('#multiselect1').data('kendoMultiSelect')) {
alert('multiselect exists - destroying and recreating');
$('#multiselect1').data('kendoMultiSelect').destroy();
$('#multiselect1').unwrap('.k-multiselect').show().empty();
$(".k-multiselect-wrap").remove();
$("#multiselect1").kendoMultiSelect({
dataSource: {
data: ["Three3", "Four4"]
}
});
$('#text2').text('Multiselect AFTER calling destroy');
}
});
When you clear the html of multiselect1, it still leaves behind all the other dom elements created when the input is turned into a widget.
Then when you recreate it, it doesn't quite handle it as well as the dropdownlist, which I think could be a bug.
If you instead wrap the controls you need to recreate in a div element and clear that element instead, it will get rid of all the extra elements so that you can start will a clean slate to create the new one.
http://dojo.telerik.com/#Stephen/EDaYA
<div id='multiselectDiv'>
<input id="multiselect1" />
</div>
$('html').on('click', '#destroy2', function(e){
if ($('#multiselect1').data('kendoMultiSelect')) {
alert('multiselect exists - destroying and recreating');
$('#multiselect1').data('kendoMultiSelect').destroy();
$('#multiselectDiv').empty();
$('#multiselectDiv').append('<input id="multiselect1" />')
$("#multiselect1").kendoMultiSelect({
dataSource: {
data: ["Three3", "Four4"]
}
});
I updated you code to this and it works:
$('html').on('click', '#destroy2', function(e){
if ($('#multiselect1').data('kendoMultiSelect')) {
alert('multiselect exists - destroying and recreating');
var multiSelect = $('#multiselect1').data("kendoMultiSelect");
multiSelect.value([]);
$("#multiselect1").remove();
$("#multiselect1").kendoMultiSelect({
dataSource: {
data: ["Three3", "Four4"]
}
});
$('#text2').text('Multiselect AFTER calling destroy');
}
});
Use remove will work once
Related
We're using CFLayout to create a tab structure in our web application. After creation of that layout we call this function:
mytabs = ColdFusion.Layout.getTabLayout("#attributes.cflayoutName#");
mytabs.on('tabchange',
function(tablayout,tab) {
var tabtitle = tab.title;
alert(tabtitle); // Actual code does various useful 'stuff' here.
}
);
That piece of code works very well, and the alert will show each time the user clicks on a tab.
The problem is that we are now trying to do the same thing with a CFLayout type of "accordion", and I cannot get an event to fire when the user switches which accordion pane they are looking at. We've tried leaving the above as is, as well as changing the "tabchange" attribute to "expand", "beforeexpand", "activate", and "collapse".
For this testing I'm using the following simple JS function to avoid issues arising from the JS within the onchange event:
mytabs = ColdFusion.Layout.getAccordionLayout("#attributes.cflayoutName#");
mytabs.on('expand',
function(tablayout,tab) {
console.log('test');
}
);
We do not receive any errors. Nothing is logged to the console at all. I've tried replacing the console.log to an alert to rule out any problems with that line.
I found that the Ext library documentation to be very helpful with finding a solution to this problem: here.
The Ext library has a getComponent method that allows you to reference the accordion layout panel that you are trying to add the expand event to. Once you have this, you can use the "on" method you are using above to assign the expand event to each panel individually.
for (x=1; x<accordionLayoutArray.length; x++) {
mytabs.getComponent(accordionPanelName).on('expand',
function(tab) { ... });
}
This became too long for a comment so adding as an answer
After some Google searches I found what I think are some related posts. It appears as though the accordion in Ext JS does not have the same events as the tab. Instead you need to add a listener in order to catch the expanding.
See this post - in case something happens to that page here is the relevant piece:
You'd need to listen to the expand event of the child panels in the accordion, you could do something like:
Code:
myAccordion.add(myFunc('myTitle'));
function myFunc(title)
{
return new Ext.Panel(
{
title: title,
listeners: { 'expand': {fn: something, scope: foo}}
}
);
}
And I also found another similar post here on SO - see both answers
Once you know that the accordion needs a listener you can find a number of results on Google. Such as How do I attach an event handler to a panel in extJS?
This Google search will give you lots of examples.
Hope that helps.
I am using RPNiemeyer`s kendo-knockout library. I have a kendo window:
HTML:
<div data-bind="kendoWindow: {isOpen: isOpen, title:'Language', width: 400, height: 200, modal: true, widget: popUpWindow }" >
JavaScript part that centers the window:
this.popUpWindow = ko.observable();
self.isOpen.subscribe(function (newValue) {
if (newValue) {
self.popUpWindow().center();
}
});
I am using the source code from my previous question for my fiddle:
Kendo-Knockout: Window does not close correctly
I am following the steps shown here:
Kendo-Knockout: How to center window
I am defining the widget observable but when I want to use it it is not filled with the actual widget.
Fiddle:
http://jsfiddle.net/dcYRM/15/
Any help with working example will be greatly appreciated.
Looks like there are a couple of issues:
First, your isOpen subscription is running before the widget has been filled.
Secondly, after filling the widget, it is causing the datasource to get refreshed and is trying to serialize the model including the widget, which is causing an issue. This is ultimately because Knockout-Kendo is a little too aggressive about unwrapping the data passed to the grid.
I see two pretty easy ways to fix the issue. The easiest way is to set up a global handler for the widget's open event and call center on it.
Putting this with the close event from a previous question would look something like:
ko.bindingHandlers.kendoWindow.options = {
close: function() {
$('.k-window, .k-overlay').remove();
},
open: function(event) {
event.sender.center();
}
};
Now, whenever any window is opened it will get centered and you don't need to mess with the widget at all. Sample here: http://jsfiddle.net/rniemeyer/F4JGG/
That looks like the best option. To make it work with the reference to the widget itself, you will need to workaround an issue in the library. As mentioned above, it is a little too aggressive and unwrapping the options and it appears that this causes an issue when a widget is initialized, the widget parameter is passed, and it is already filled with a widget. I should be able to address it in the library, when I get a chance.
Otherwise, you would have to do:
self.popUpWindow = ko.observable();
self.popUpWindow.subscribe(function (widget) {
if (widget) {
widget.center();
self.popUpWindow(null); //hack - Knockout-Kendo should handle this one
}
});
So, clear the observable after you called center on it. Here is a sample: http://jsfiddle.net/rniemeyer/PVMjy/. I also subscribed to the widget's observable itself, so that there is not the timing issue with isOpen as mentioned above.
Setting the global open handler, seems like the cleanest and best option in this case.
I use pie.js (http://css3pie.com/) a library that allows me to have css3 on ie6+. I apply pie.js on document.ready :
$(document).ready(function(){
if (window.PIE) {
$('.vexClass, #vexId').each(function(){
PIE.attach(this);
});
}
});
My problem is when the dom was modified the elements for which I have apply pie.js are not rendering well so I must load my function that apply pie.js to my elements when the dom is modified and only for section that was modified using OnSubtreeModified or another technique ... For example I have a panel that is displayed when dom was loaded, in that panel I have a button that is expanding another panel with buttons and other elements for which I want to apply pie.js, so for main panel all elements for which I have applied pie.js are rendered ok but the elements from expanded panel, pie.js is not applied :|
So how can I inject that pie.js when dom is modified for expanded panel?
Thank's.
If you are using wicket the only way to do that is through Wicket.Ajax.registerPostCallHandler
Add this in your project, every time when a ajax request is done your pie will be applied.
window.document.ready = function() {
Wicket.Ajax.registerPostCallHandler(pieStarter);
}
function pieStarter() {
if (window.PIE) {
go();
}
}
function go() {
$(''.vexClass, #vexId'').each(function() {
PIE.attach(this);
});
}
Do you get your data from an Ajax call? If so, you need to add the pie in the callback. There is no possibility (at least afaik) to add anything on something that you don't have at a given time.
If you need to add the pie in the callback, you should make an separate function for that. You can give a dom node to the function and it checks for every child node if pie is applied, and if not it does so.
You can't use the DOMSubtreeModified event since older versions of IE - which you are targeting - don't support this. You can, however, work around this by using this answer Detect changes in the DOM together with this script:
$(document).ready(function () {
function applyPIE() {
if (window.PIE) {
$('.vexClass, #vexId').not('[data-pie=attached]').each(function(){
PIE.attach(this);
$(this).attr('data-pie', 'attached');
});
}
}
onDomChange(function(){
alert('document updated, applying PIE now');
applyPIE();
});
$('#link').click(function () {
$('body').append('<h4>added content</h4>');
});
applyPIE();
});
It creates a local function applyPIE() which is called on document ready and every time the DOM changes - filtering out already processed elements. You may want to expand the .vexClass, #vexId set of selectors to match your needs.
JSFiddle: http://jsfiddle.net/hongaar/y3FaM/1/
A few of my views need their textareas converted to rich text editors.
I'm using jwysiwyg as the editor. It requires that the element it is being attached to is in the page when the editor is initialized i.e. when I call $(this.el).wysiwyg(), this.el is already in the document.
Most of my views do not actually attach themselves to the dom - their render methods simply set their elements html content using the apps templating engine e.g. $(this.el).html(this.template(content)
Views/Controllers further up the chain look after actually inserting these child views into the page. At the same time, views do re-render themselves when their models change.
How do I ensure that the editor is attached to the element every time its rendered and still ensure that the editor is not attached until the element is already in the page?
Obviously I could hack something together that would work in this particular case but I would like an elegant solution that will work for all cases.
Any help would be much appreciated.
Edit: The main point here is that the solution must scale gracefully to cover multiple elements that must be styled after rendering and must not be styled until they are in the DOM
Edit: This is not an issue if I do top-down rendering but this is slow, I'd like a solution whereby I can render from the bottom up and then insert the complete view in one go at the top
Edit:
Using a combination of some of the techniques suggested below I'm thinking of doing something like the following. Any comments/critique would be appreciated.
app/views/base_view.js:
initialize: function() {
// wrap the render function to trigger 'render' events
this.render = _.wrap(this.render, function() {
this.trigger('render')
});
// bind this view to 'attach' events.
// 'attach' events must be triggered manually on any view being inserted into the dom
this.bind('attach', function() {
this.attached();
// the refreshed event is only attached to the render event after the view has been attached
this.bind('render', this.refreshed())
// each view must keep a record of its child views and propagate the 'attach' events
_.each(this.childViews, function(view) {
view.trigger('attach')
})
})
}
// called when the view is first inserted to the dom
attached: function() {
this.style();
}
// called if the view renders after it has been inserted
refreshed: function() {
this.style();
}
style: function() {
// default styling here, override or extend for custom
}
What if you used the JQuery LiveQuery Plugin to attach the editor? Such code could be a part of your template code, but not as HTML, but as Javascript associated with the template. Or you could add this globally. The code might look like this (assuming you've included the plugin itself):
$('textarea.wysiwyg').livequery(function() {
$(this).wysiwyg();
});
I have not tested this code, but in theory it should match an instance of a textarea element with a class of 'wysiwyg' when it appears in the DOM and call the wysiwyg function to apply the editor.
To adhere to DRY principle and get an elegant solution, you'll want a function dedicated to determining if a textarea has wysiwyg, let's say wysiwygAdder and add it if necessary. Then you can use underscore's wrap function to append your wysiwyg adder to the end of the render function.
var View = Backbone.View.extend({
el: '#somewhere',
initialize: function(){
_.bind(this, "render");
this.render = _.wrap(this.render, wysiwygAdder);
},
render: function(){
//Do your regular templating
return this;//allows wysiwygAdder to pick up context
}
});
function wysiwygAdder(context){
$('textarea', context).doYourStuff();
//check for presence of WYSIWYG, add here
}
When the view is initialized, it overwrites your render function with your render function, followed by wysiwygAdder. Make sure to return this; from render to provide context.
One solution would be to use event delegation and bind the focus event to check whether the rich text editor had been loaded or not. That way the user would get the text editor when they needed it (via the lazy loading, a minor performance improvement) and you wouldn't have to load it otherwise. It would also eliminate needing to worry about when to attach the rich text editor and that being dependent on the rendering chain.
If you're worried about the FOUC (flash of unstyled content) you could simply style the un-modified text areas to contain an element with a background image the looked just like the wysiwyg controls and have your focus binding toggle a class to hide the facade once the rich text editor had taken over.
Here's a sample of what I had in mind:
var View = Backbone.View.extend({
el: '#thing',
template: _.template($("#template").html()),
render: function() {
// render me
$(this.el).html(this.template(context));
// setup my textareas to launch a rich text area and hide the facade
$(this.el).delegate('focus', 'textarea', function() {
if(!$(this).hasRichTextEditor()) { // pseudocode
$(this).wysiwyg();
$(this).find('.facade').toggle();
}
});
}
});
Great problem to solve! Not too sure I've got the entire jist but... You may be able to get away with a 'construction_yard' (I just made that term up) that's way off to the left, build and place items there, then just move them when they're ready to be placed. Something along the lines of:
.construction_yard {
position: absolute;
left: -10000000000px;
}
This solution may fix several problems that might crop up. For example jquery height and width attributes on something that's 'hidden' are 0, so if you are styling along those lines, you'd have to wait till it was placed, which is more complicated, and jumbles things up.
your views would then need to do something along the lines of (pseudo-code):
//parent
//do all your view-y stuff...
foreach (child = this.my_kids) {
if child.is_ready() {
put_that_child_in_its_place();
}
}
Similarly, for children, you'd do a similar thing:
//child
foreach(parent = this.my_parents) {
if parent.isnt_ready() {
this.go_play_in_construction_yard();
} else {
this.go_to_parents_house();
}
}
... and, since backbone is pretty easy to extend, you could wrap it up in a more generalized class using:
var ParentsAndChildrenView = Backbone.View.extend({blah: blah});
and then
var Wsywig = ParentsAndChildrenView.extend({blah: blah});
hope that helps!
Almost forgot to note my source:
http://jqueryui.com/demos/tabs/#...my_slider.2C_Google_Map.2C_sIFR_etc._not_work_when_placed_in_a_hidden_.28inactive.29_tab.3F
I am using this jquery plugin for called tokeninput it's for autosuggestion when typing:
the code here generates the autosuggestion for the input box:
$("#input-auto").tokenInput("topicGet.php", {
theme: "facebook"
});
but when I try to append the input which uses the #input-auto it does not do the autosuggestion but it works when it's loaded on the page:
the code is here:
$('.topicEdit').live('click', function() {
$('#Qtopics').html('<input type="text" id="input-auto" />');
I'm trying to solve the problem, but I can't find anything, I also tried to put the live click on the .topicEdit, but it's still not working. :)) thanks
Looks like you're probably initializing the autosuggestion BEFORE you add the input into the DOM. You have to remember that jQuery is stupid, and only does things in the order that you tell it. When you initially 'generate' the autosuggestion, if it can't find it in the DOM at that moment, it won't do anything! So, to solve your problem, you'll want to do the generation of the autosuggest after you insert it into the DOM:
$('.topicEdit').live('click', function() {
$('#Qtopics').html('<input type="text" id="input-auto" />');
$("#input-auto").tokenInput("topicGet.php", {
theme: "facebook"
});
});
yay?
Calling tokenInput on page load when input-auto doesn't yet exist is your problem.
$('.topicEdit').live('click', function() {
$('#Qtopics').html('<input type="text" id="input-auto" />');
$("#input-auto").tokenInput("topicGet.php", {
theme: "facebook"
});
});
I'm not exactly sure what the problem is, but try using livequery plugin. You don't need to specify an event to it, just a function which will run for all current and future elements that match the selector.
Example:
$(".something").livequery(function () {
// do something with the element
}