We have a number of jQuery DataTables that all use server side processing. We have paging and sorting set up, and all is working well. In these tables there is at least one column of checkboxes to allow selecting of rows to do some kind of processing on. We want to confirm that the user wishes to page or sort if there are any checkboxes checked.
What I thought I could do (and can't)
"fnPreDrawCallback" : function(table) {
if (CullAddress.AddressIsChecked()) {
var $warningDiv = $('div#pageWarning');
var warningText = "One or more Addresses are selected for Excluding or Tagging. Are you sure you wish to nvaigate away?";
$warningDiv.find("div#pageWarningText").html(warningText);
$warningDiv.dialog({
resizable: false,
height: "auto",
width: "auto",
modal: true,
buttons: {
"Leave Page": function () {
CullAddress.resetWarningText();
$warningDiv.dialog('close');
},
"Stay On Page": function () {
CullAddress.resetWarningText();
$warningDiv.dialog('close');
return false;
}
},
});
}
},
Initially I thought this would be simple, but now, it is getting a bit hefty, and I am not sure of what the right way is. I am trying to use the fnPreDrawCallback, and initially I intended to create and display a jQueryUI Dialog, and have the buttons determine whether or not to return false; out of the callback thus staying on the page, or allowing the page/sort to go through.
I now understand that javaScript does not work that way. I suspect I will have to do the following, but before I go through that trouble I want to ask if there is a more concise (and reusable) way of doing this.
In fnPreDrawCallback, get properties to describe the intended set page/sort (e.g. offset, pageSize, sSortDir, iSortCol, etc).
Determine via dialog whether to continue or stay on page
Use aforementioned properties to construct the GET for the datatable to bypass the fnPreDrawCallback
Am I making this more difficult that it needs to be? Surely I am not the first person to want to do this, but for the life of me, I can find an example, or I cant figure out the keywords I should be searching for...
Any helps?
Link to working example: http://jsfiddle.net/6frQZ/3/
As already discussed in the comments to the question, I tried to circumvent the default behaviour of DataTables to fit your needs and created an example on jsFiddle to show, including numbered-pagination and sorting.
Basically, you'll need to unbind the event-handlers, that the DataTables - plugin binds to it's components, like so:
$('.dataTables_paginate a').unbind();
$('.dataTables_wrapper thead th').unbind();
Using .unbind without a parameter will unbind any event-listener on the element, so be careful when using this.
Gladly, the DataTables - API provides functions that let you call the internal paging and sorting-methods yourself, named fnSort (API-Link) and fnPageChange (API-Link).
To keep it simple, i just used a confirm - Box to ask for the user-interaction:
var userInteraction = confirm("Do you really want to change the page?");
if(userInteraction){
oTable.fnPageChange(dir);
$('.dataTables_paginate span a').unbind();
}
but all you'd need to do is call the DataTables-functions inside of your "Leave Page" - callback you already provided in the code.
Note: When it comes to the numbered buttons of the paging: It seems like DataTables regenerates those everytime the paging is changed, thus I need to unbind the event-Handlers again after every page-change.
The rest is simple yet not very elegant code, in which I just look for certain classes to know, what button was clicked or which state the sorting-header is in.
Excerpt:
var dir = "",
$this = $(this);
if($this.hasClass('previous')){
dir = "previous";
}else if ($this.hasClass('next')){
dir = "next";
}else if($this.hasClass('first')){
dir = "first";
}else if($this.hasClass('last')){
dir = "last";
}else{
dir = parseInt($this.text(),10)-1;
}
Related
I am trying to toggle a view between grid and list view mode on my frontend HTML page. I am able to do this fine with dom and HTML classes manipulation by toggling "display: none" between two containers. However, when I go to the next product page(through pagination) or when I reload the page, the default view is the one that appears and not the one that was last toggled. Is there a way to persist the view in case a page reload or product pagination changes? thank you.
here is the dom code that achieves this :
viewList.addEventListener('click', function() {
this.classList.add('view__active');
viewGrid.classList.remove('view__active');
gridItem.classList.add('hidden');
listItem.classList.remove('hidden');
});
viewGrid.addEventListener('click', function() {
this.classList.add('view__active');
viewList.classList.remove('view__active');
gridItem.classList.remove('hidden');
listItem.classList.add('hidden');
});
So far I found that I have to use localStorage to achieve this. but is there a better way to do this?
Essentially what is happening is when you request something from the server, the server responds with an HTML document, and whichever scripts associated with that document is run, So whatever JS executed in the first request is not in context when the second request(paginate or reload) is made.
So you need a way to persist information across these page loads, For that, you have 3 options.
Use sessionStorage.
Use localStorage
Use Cookies.
Of the 3 above the easiest would be to use either option 1 or 2.
Replying to your comment,
Also, If I am using localStorage, What am I using to store the view state?
I'm not quite clear as to what you mean by "What you are using to store the state" If your question is about where your data is stored, you need not worry about it as this is handled by the browser. If your question is about "How" to store it you can go through the MDN docs attached in option 1 or 2. This is simply storing a key-value pair as shown in the docs
localStorage.setItem('preferedView', 'grid'); You can add this to your on click handlers as follows,
viewList.addEventListener('click', function() {
this.classList.add('view__active');
viewGrid.classList.remove('view__active');
gridItem.classList.add('hidden');
listItem.classList.remove('hidden');
localStorage.setItem('preferedView', 'grid');
});
viewGrid.addEventListener('click', function() {
this.classList.add('view__active');
viewList.classList.remove('view__active');
gridItem.classList.remove('hidden');
listItem.classList.add('hidden');
localStorage.setItem('preferedView', 'list');
});
Then when loading a new page at the top of your script you can get the users preferedView(if existing) via const preferedView = localStorage.getItem('preferedView');
Here is a complete example from MDN
In order for anyone to find an answer for a similar task, thanks to #Umendra insight, I was able to solve this by using this :
function viewToggeler(viewBtn1, viewBtn2, view1, view2, viewStord) {
viewBtn2.classList.add('view__active');
viewBtn1.classList.remove('view__active');
view1.classList.add('hidden');
view2.classList.remove('hidden');
sessionStorage.setItem('preferedView', viewStord);
}
viewList.addEventListener('click', () => {
viewToggeler(viewGrid, viewList, gridItem, listItem, 'list');
});
viewGrid.addEventListener('click', () => {
viewToggeler(viewList, viewGrid, listItem, gridItem, 'grid');
});
if (sessionStorage.getItem('preferedView') === 'grid') {
viewToggeler(viewList, viewGrid, listItem, gridItem, 'grid');
} else if (sessionStorage.getItem('preferedView') === 'list') {
viewToggeler(viewGrid, viewList, gridItem, listItem, 'list');
}
I ended up using sessionStorage over localStorage because it empties itself on window/tab closing which might be the most desirable result. localStorage persists even after exiting the browser and opening it back.
Also, at any point someone wants to empty the sessionStorage on exit, I used :
window.addEventListener('onbeforeunload', () => {
sessionStorage.removeItem('preferedView');
});
(I don't quite know what to call this kind of control, so if someone can tell me what the name is, I'll edit the question for clarity.)
I'm looking for a jQuery control that will let me make a little pop-up editor that looks like a balloon coming out from a point in the form. In my use case, I'm tight on space and I want to let the user pick a couple of date ranges.
Something like this in the iCloud Calendar new event pop-up:
I ended up using qTip2 for jQuery:
http://qtip2.com/
But to get it to put a who div in there, I had to customize the JS a bit:
events: {
show: function(event, api) {
$("#tooltipContainer").html("");
$("#btnSelectDatesPopup").detach().appendTo("#tooltipContainer");
$("#btnSelectDatesPopup").show();
},
hide: function(event, api) {
$("#btnSelectDatesPopup").hide();
$("#btnSelectDatesPopup").detach().appendTo("#originalSelectDatesPopupContainer");
$("#tooltipContainer").html("<p>...</p>");
}
}
And on the form submit, close the tooltip if it's not closed already, and put it back into the DOM where it belongs:
// On form submit, trigger the tooltip hide to preserve user-chosen values
$("#aspnetForm").submit("submit", function(event) {
qtipApi.hide();
setTimeout(function(){}, 300);
});
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.
I need to be able to change the filebrowserUploadUrl of CKEditor when I change some details on the page, as the querystring I pass through is used by the custom upload process I've put in place.
I'm using the JQuery plugin. Here's my code:
$('#Content').ckeditor({
extraPlugins: 'autogrow',
autoGrow_maxHeight: 400,
removePlugins: 'resize'
});
$("#Content").ckeditorGet().on("instanceReady", function () {
this.on("focus", function () {
// Define browser Url from selected fields
this.config.filebrowserUploadUrl = filebrowserUploadUrl: '/my-path-to-upload-script/?ID1=' + $("ID1").val() + '&ID2=' + $("#ID2").val();
});
});
This works fine the first time, but if I come out of the dialogue and change the value of #ID1 and #ID2, it keeps the previous values. When I debug, the filebrowserUploadUrl is set correctly, but it doesn't affect the submission values. It seems the config values are cached.
Is there any way to change a config value on the fly?
Currently I don't see any possibility to change this URL on the fly without hacking.
Take a look at http://dev.ckeditor.com/browser/CKEditor/trunk/_source/plugins/filebrowser/plugin.js#L306
This element.filebrowser.url property is set once and as you can see few lines above it will be reused again. You can try to somehow find this element and reset this property, but not having deeper understanding of the code of this plugin I don't know how.
Second option would be to change this line #L284 to:
url = undefined;
However, I haven't check if this is the correct solution :) Good luck!
BTW. Feel free to fill an issue on http://dev.ckeditor.com.
I solved this by reloading the editor whenever a change occurred; I actually went through the source code for the browser plugin etc, but couldn't get any changes to work (and of course, I really didn't want to change anything for future upgrades).
function setFileBrowserUrl() {
// Remove editor instance
$("#Content").ckeditorGet().destroy();
// Recreate editor instance (needed to reset the file browser url)
createEditor();
}
function createEditor() {
$('#Content').ckeditor({
filebrowserUploadUrl: '/my-path-to-upload-script/?ID1=' + $("ID1").val() + '&ID2=' + $("#ID2").val(),
extraPlugins: 'autogrow',
autoGrow_maxHeight: 400,
removePlugins: 'resize'
});
}
Then I call setFileBrowserUrl every time the relevant elements on the page change. Not ideal, but it works for my purposes :)
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.