So I have a table, with which I need to be able to sort and filter (form submit) as well as being able to show and hide columns. I have a simple checkbox that does nothing but toggle the columns. I have been able to get that to work properly, but my problem now is that I need to save the state throughout the entire page lifecycle -- which doesn’t happen.
The basic column show/hide is in the page itself, right after the table:
#section Scripts {
<script type="text/javascript">
$('input[name="columnControl"]').on('switchChange.bootstrapSwitch', function (event, state) {
if (state) {
$("th.status").removeClass("columnHide");
$("td.status").removeClass("columnHide");
$("th.information").addClass("columnHide");
$("td.information").addClass("columnHide");
$("td#p1").attr('colspan', 4);
$("td#p2").attr('colspan', 6);
localStorage.setItem('columnControl', 'true');
} else {
$("th.status").addClass("columnHide");
$("td.status").addClass("columnHide");
$("th.information").removeClass("columnHide");
$("td.information").removeClass("columnHide");
$("td#p1").attr('colspan', 2);
$("td#p2").attr('colspan', 3);
localStorage.setItem('columnControl', 'false');
}
});
</script>
}
So please note: the above DOES WORK, at least in terms of showing and hiding the columns.
Now, the best method I have found so far for saving information through the page cycle is via the localstorage setting, as seen in the code above. This works splendidly for tabs on other pages, but I have been unable to get it to work for my table and bootstrap switch.
Specifically:
On page load, I want the switch to be conditional on the state of the localstorage setting. If it is true or false, the switch needs to be at the appropriate setting, and the appropriate classes need to be set for the columns. If there is no localstorage content (no True or False stored), I need the switch to be ON (true) and certain classes set (a default case).
On form submit (a GET, not a POST), I need to have the switch and all applied classes to remain the same as they were, and to NOT revert to a default case.
If the user leaves the page and returns to it, the switch and classes should remain at their last state and to not revert to the default.
The best I have been able to come up with is this:
#section Scripts {
<script type="text/javascript">
$( document ).ready(function() {
var columnState = localStorage.getItem('columnControl'); //grab the localstorage value, if any
if (columnState) { //does the localstorage value even exist?
$('input[name="columnControl"]').bootstrapSwitch('setState', columnState); //if localstorage exists, set bootstrap switch state based off of localstorage value
if (columnState == 'true') { //if localstorage value == true
$("th.status").removeClass("columnHide");
$("td.status").removeClass("columnHide");
$("th.information").addClass("columnHide");
$("td.information").addClass("columnHide");
$("td#p1").attr('colspan', 4); //set tfoot colspans
$("td#p2").attr('colspan', 6);
} else { //if localstorage value == false
$("th.status").addClass("columnHide");
$("td.status").addClass("columnHide");
$("th.information").removeClass("columnHide");
$("td.information").removeClass("columnHide");
$("td#p1").attr('colspan', 2); //set tfoot colspans
$("td#p2").attr('colspan', 3);
}
} else { //if localstorage value doesn't exist, set default values
$('input[name="columnControl"]').bootstrapSwitch('setState', true);
$("th.information").addClass("columnHide");
$("td.information").addClass("columnHide");
}
});
$('input[name="columnControl"]').on('switchChange.bootstrapSwitch', function (event, state) {
… first script, above …
});
</script>
}
The actual Bootstrap Switch is initialized by a global JS file, via
$("input[type=checkbox]").bootstrapSwitch(); // checkbox toggle
which appears in the head of the webpage.
I have two groups of columns, information and status which comprise 80% of the columns between them. Three columns have no flag for being hidden or unhidden, because they are meant to be displayed at all times.
for test if the local storage exist use :
columnState === null
You just need to fire the switchChange event after define the default value
Full exemple :
#section Scripts {
<script type="text/javascript">
$( document ).ready(function() {
$('input[name="columnControl"]').on('switchChange.bootstrapSwitch', function (event, state) {
if (state) {
$("th.status").removeClass("columnHide");
$("td.status").removeClass("columnHide");
$("th.information").addClass("columnHide");
$("td.information").addClass("columnHide");
$("td#p1").attr('colspan', 4);
$("td#p2").attr('colspan', 6);
localStorage.setItem('columnControl', true);
} else {
$("th.status").addClass("columnHide");
$("td.status").addClass("columnHide");
$("th.information").removeClass("columnHide");
$("td.information").removeClass("columnHide");
$("td#p1").attr('colspan', 2);
$("td#p2").attr('colspan', 3);
localStorage.setItem('columnControl', false);
}
});
var columnState = localStorage.getItem('columnControl'); //grab the localstorage value, if any
if (columnState === null) { //does the localstorage value even exist?
columnState = true //if localstorage value doesn't exist, set default values
}
$('input[name="columnControl"]').bootstrapSwitch('setState', columnState);
</script>
}
Related
I am having a very weird problem. I am trying to load a "Group" which has a list of devices. The devices exist in a table and a checkmark should appear when the device is inside the group.
My code looks like this:
function LoadGroup(Input)
{
document.querySelectorAll('input[type=checkbox]').forEach(el => el.checked = false);
console.log(Input.id);
GroupData.find(Element => {
if(Element["ID"] == Input.id)
{
Element["Devices"].forEach(Device => {
Checkmark = document.getElementById(Device["DeviceID"]);
Checkmark.checked = true
console.log(Checkmark.checked);
})
return true;
}
});
document.querySelectorAll('input[type=checkbox]').forEach(Element => console.log(Element.checked));
}
So a group gets clicked on, I search for the right Group in my data and then go through the devices to set the Checkmark and log the current state. I get 4 true in the console.
After this I query all checkmarks and check if they are checked. I get 3 true one false.
I dont understand what the problem is. He sets all true but then one is false.
Any ideas?
Basically, I have an appointment form which is broken down into panels.
Step 1 - if a user clicks london (#Store1) then hide Sunday and Monday from the calendar in panel 5.
Basically, I want to store this click so that when the user gets to the calendar panel, it will know not to show Sunday and Monday
$('#store1').click(function () {
var $store1 = $(this).data('clicked', true);
console.log("store 1 clicked");
$('.Sunday').hide();
$('.Monday').hide();
});
after I have captured this in a var I then want to run it when the calendar displays.
function ReloadPanel(panel) {
return new Promise(function (resolve, reject, Store1) {
console.log(panel);
console.log("finalpanel");
panel.nextAll('.panel').find('.panel-updater').empty();
panel.nextAll('.panel').find('.panel-title').addClass('collapsed');
panel.nextAll('.panel').find('.panel-collapse').removeClass('in');
var panelUpdater = $('.panel-updater:eq(0)', panel),
panelUrl = panelUpdater.data('url');
if (panelUpdater.length) {
var formData = panelUpdater.parents("form").serializeObject();
panelUpdater.addClass('panel-updater--loading');
panelUpdater.load(panelUrl, formData, function (response, status) {
panelUpdater.removeClass('panel-updater--loading');
if (status == "error") {
reject("Panel reload failed");
} else {
resolve("Panel reloaded");
}
});
} else {
resolve("no reloader");
}
});
}
I'm not sure if this is even written right, so any help or suggestions would be great
Thanks in advance
Don't think of it as "storing a click". Instead, consider your clickable elements as having some sort of data values and you store the selected value. From this value you can derive changes to the UI.
For example, consider some clickable elements with values:
<button type="button" class="store-button" data-store-id="1">London</button>
<button type="button" class="store-button" data-store-id="2">Paris</button>
<button type="button" class="store-button" data-store-id="3">Madrid</button>
You have multiple "store" buttons. Rather than bind a click event to each individually and customize the UI for each click event, create a single generic one which captures the clicked value. Something like:
let selectedStore = -1;
$('.store-button').on('click', function () {
selectedStore = $(this).data('store-id');
});
Now anywhere that you can access the selectedStore variable can know the currently selected store. Presumably you have some data structure which can then be used to determine what "days" to show/hide? For example, suppose you have a list of "stores" each with valid "days":
let stores = [
{ id: 1, name: 'London', days: [2,3,4,5,6] },
// etc.
];
And your "days" buttons have their corresponding day ID values:
<button type="button" class="day-button" data-day-id="1">Sunday</button>
<button type="button" class="day-button" data-day-id="2">Monday</button>
<!--- etc. --->
You can now use the data you have to derive which buttons to show/hide. Perhaps something like this:
$('.day-button').hide();
for (let i in stores) {
if (stores[i].id === selectedStore) {
for (let j in stores[i].days) {
$('.day-button[data-day-id="' + stores[i].days[j] + '"]').show();
}
break;
}
}
There are a variety of ways to do it, much of which may depend on the overall structure and flow of your UX. If you need to persist the data across multiple pages (your use of the word "panels" implies more of a single-page setup, but that may not necessarily be the case) then you can also use local storage to persist things like selectedStore between page contexts.
But ultimately it just comes down to structuring your data, associating your UI elements with that data, and performing logic based on that data to manipulate those UI elements. Basically, instead of manipulating UI elements based only on UI interactions, you should update your data (even if it's just in-memory variables) based on UI interactions and then update your UI based on your data.
you can use the local storage for that and then you can get your value from anywhere.
Set your value
localStorage.setItem("store1", JSON.stringify(true))
Get you value then you can use it anywhere:
JSON.parse(localStorage.getItem("store1"))
Example:
$('#store1').click(function() {
var $store1 = $(this).data('clicked', true);
localStorage.setItem("store1", JSON.stringify(true))
console.log("store 1 clicked");
$('.Sunday').hide();
$('.Monday').hide();
});
I am building a pretty combobox with checkboxes and conditional entries. Everything works out alright, except for two features that I cannot figure out how to implement.
1) I would like to move the label inside the combobox, make it shift the values to the right, and appear in a slightly gray color.
2) I would like the value to ignore certain entries (group headers) selected. Those entries are there for functionality only - to select/unselect groups of other entries.
The entire project is in the zip file. You don't need a server, it's a client base app. Just download the archive, unpack, and launch app.html in your browser.
http://filesave.me/file/30586/project-zip.html
And here's a snapshot of what I would like to achieve.
Regarding your second issue, the best way I see is to override combobox onListSelectionChange to filter the values you don't want:
onListSelectionChange: function(list, selectedRecords) {
//Add the following line
selectedRecords = Ext.Array.filter(selectedRecords, function(rec){
return rec.data.parent!=0;
});
//Original code unchanged from here
var me = this,
isMulti = me.multiSelect,
hasRecords = selectedRecords.length > 0;
// Only react to selection if it is not called from setValue, and if our list is
// expanded (ignores changes to the selection model triggered elsewhere)
if (!me.ignoreSelection && me.isExpanded) {
if (!isMulti) {
Ext.defer(me.collapse, 1, me);
}
/*
* Only set the value here if we're in multi selection mode or we have
* a selection. Otherwise setValue will be called with an empty value
* which will cause the change event to fire twice.
*/
if (isMulti || hasRecords) {
me.setValue(selectedRecords, false);
}
if (hasRecords) {
me.fireEvent('select', me, selectedRecords);
}
me.inputEl.focus();
}
},
And change your onBoundlistItemClick to only select and deselect items in the boundlist not to setValue of the combo:
onBoundlistItemClick: function(dataview, record, item, index, e, eOpts) {
var chk = item.className.toString().indexOf('x-boundlist-selected') == -1;
if ( ! record.data.parent) {
var d = dataview.dataSource.data.items;
for (var i in d) {
var s = d[i].data;
if (s.parent == record.data.id) {
if (chk) { // select
dataview.getSelectionModel().select(d[i],true);
} else { // deselect
dataview.getSelectionModel().deselect(d[i]);
}
}
}
}
},
Regarding your first issue, it is easy to add the label using the displayTpl config option. But this will only add the text you need, without any style (grey color, etc). The combo is using a text input, which does not accept html tags. If you don't need the user to type text, than you may want to change the combo basic behavior and use another element instead of the text input.
I currently have a rather big Grid and am successfully using the RowExpander plugin to display complementary informations on certain rows. My problem is that it's not all rows that contain the aforementioned complementary informations and I do not wish the RowExpander to be active nor to show it's "+" icon if a particular data store's entry is empty. I tried using the conventional "renderer" property on the RowExpander object, but it did not work.
So basically, how can you have the RowExpander's icon and double click shown and activated only if a certain data store's field != ""?
Thanks in advance! =)
EDIT: I found a solution
As e-zinc stated it, part of the solution (for me at least) was to provide a custom renderer that would check my conditional field. Here is my RowExpander:
this.rowExpander = new Ext.ux.grid.RowExpander({
tpl: ...
renderer: function(v, p, record) {
if (record.get('listeRetourChaqueJour') != "") {
p.cellAttr = 'rowspan="2"';
return '<div class="x-grid3-row-expander"></div>';
} else {
p.id = '';
return ' ';
}
},
expandOnEnter: false,
expandOnDblClick: false
});
Now, the trick here is that for this particular Grid, I chose not to allow the expandOnEnter and expanOnDblClick since the RowExpander will sometimes not be rendered. Also, the CSS class of the grid cell that will hold the "+" icon is 'x-grid3-td-expander'. This is caused by the fact that the CSS class is automatically set to x-grid3-td-[id-of-column]. So, by setting the id to '' only when I'm not rendering the rowExpander, I'm also removing the gray background of the un-rendered cells. So, no double click, no enter, no icon, no gray-background. It really becomes as if there is strictly no RowExpander involved for the columns where my data store field is empty (when I want no RowExpander).
That did the trick for me. For someone that wishes to preserve the ID of the cell, or that wishes to keep the double click and enter events working, there is nothing else to do other than extending the class I guess. Hope this can help other people stuck in the position I was!
As e-zinc stated it, part of the solution (for me at least) was to provide a custom renderer that would check my conditional field. Here is my RowExpander:
this.rowExpander = new Ext.ux.grid.RowExpander({
tpl: ...
renderer: function(v, p, record) {
if (record.get('listeRetourChaqueJour') != "") {
p.cellAttr = 'rowspan="2"';
return '<div class="x-grid3-row-expander"></div>';
} else {
p.id = '';
return ' ';
}
},
expandOnEnter: false,
expandOnDblClick: false
});
Now, the trick here is that for this particular Grid, I chose not to allow the expandOnEnter and expandOnDblClick specifically since the RowExpander will sometimes not be rendered. Also, the CSS class of the grid cell that will hold the "+" icon is 'x-grid3-td-expander'. This is caused by the fact that the CSS class is automatically set to x-grid3-td-[id-of-column]. So, by setting the id to an empty string only when I'm not rendering the rowExpander, I'm also removing the gray background of the cells that won't offer any expanding. So, no double click, no enter, no icon, no gray-background. It really becomes as if there is strictly no RowExpander involved for the columns where my data store field is empty (when I want no RowExpander).
That did the trick for me. For someone that wishes to preserve the ID of the cell, or that wishes to keep the double click and enter events working, there is nothing else to do other than extending the RowExpander class in my opinion. Of course, one could also use Ext.override(), but then all instances of RowExpander would be hit by the override.
I have the same task, there is my solution
var rowExpander = new Ext.ux.grid.RowExpander({
renderer : function(v, p, record){
return record.get('relatedPageCount') > 0 ? '<div class="x-grid3-row-expander"> </div>' : ' ';
}
});
I have overridden render method which test relatedPageCount field in store and render + or white space.
I think I've found a cleaner solution.Give me a feedback pls :)
I extend the toggleRow method of RowExpander and if I match a condition avoid to toggle the row.Otherwise the standard flow continues
Ext.create('customplugins.grid.plugin.ClickRowExpander',{
pluginId : 'rowexpander',
rowBodyTpl : new Ext.XTemplate(
'<p><b>Last Modified By:</b> {usermodify}</p>',
'<p><b>User data:</b> {userdata}</p>',
'<p><b>Correlation ID:</b> {correlationid}</p>',
'<p><b>Description:</b> {descr}</p>'
),
toggleRow : function(rowIdx, record) {
if(record.get('directory')) return false;
customplugins.grid.plugin.ClickRowExpander.prototype.toggleRow.apply(this, arguments);
}
})
This version works in Ext JS 5 and 6 (classic)
One thing is to remove the +/- icon, which can be done via grid viewConfig:
getRowClass: function (record, rowIndex, rowParams, store) {
var yourFieldofChoice = record.get('yourFieldofChoice');
if (yourFieldofChoice == null) {
return 'hide-row-expander';
}
},
Define css for hide-row-expander:
.hide-row-expander .x-grid-row-expander {
visibility: hidden;
}
Now you disable expanding on enter key ('expandOnEnter' config is no longer supported in Ext JS 6) or double click by overriding toggleRow, or if you do not wish the override you create your custom rowexpander built on existing plugin:
Ext.define('RowExpander', {
extend: 'Ext.grid.plugin.RowExpander',
alias: 'plugin.myExpander',
init: function (grid) {
var me = this;
me.grid = grid;
me.callParent(arguments);
},
requiredFields: ['yourFieldofChoice'],
hasRequiredFields: function (rec) {
var valid = false;
Ext.each(this.requiredFields, function (field) {
if (!Ext.isEmpty(rec.get(field))) {
valid = true;
}
});
return valid;
},
toggleRow: function (rowIdx, record) {
var me = this, rec;
rec = Ext.isNumeric(rowIdx)? me.view.getStore().getAt(rowIdx) : me.view.getRecord(rowIdx);
if (me.hasRequiredFields(rec)) {
me.callParent(arguments);
}
}
});
I have handled the beforeexpand event inside the listeners of Ext.ux.grid.RowExpander. beforeexpand method got the whole row data injected. Checking the data conditionally we can return true or false. If we return false it wont expand otherwise it will do.
var expander = new Ext.ux.grid.RowExpander({
tpl: '<div class="ux-row-expander"></div>',
listeners: {
beforeexpand : function(expander, record, body, rowIndex){
var gpdata = record.data.GROUP_VALUES[1].COLUMN_VALUE
if(gpdata == null){
return false;
}
else{
return true;
}
}
}
});
I'm teaching myself EXTjs 4 by building a very simple application.
In EXTjs 4 I've got 4 grids that each have the Grid to Grid drag/drop plugin. (Example functionality here: http://dev.sencha.com/deploy/ext-4.0.2a/examples/dd/dnd_grid_to_grid.html )
In my view I have the plugin defined as such:
viewConfig: {
plugins: {
ptype: 'gridviewdragdrop',
dragGroup: 'ddzone',
dropGroup: 'ddzone'
}
},
Now in the example, they have different dragGroups and dropGroups, but because I want the items to drag/dropped between each other fluidly, I gave the groups the same name.
The way the information gets originally populated into the 4 different lists is by looking at an the state_id in the db. All state_ids 1 go into the backlog store, 2 In Progress store, etc, etc.
So what I need to do when the item is drag/dropped, is remove it from its old store and put it into the new store (updating the state_id at the same time, so I can sync it with the db afterwards).
My only problem is figuring out the origin grid and destination grid of the row that was moved over.
Thank you!
PS. If you're curious this is what my drop event handler looks like at the moment:
dropit: function (node, data, dropRec, dropPosition) {
console.log('this');
console.log(this);
console.log('node');
console.log(node);
console.log('data');
console.log(data);
console.log('droprec');
console.log(dropRec);
console.log('dropPosition');
console.log(dropPosition);
},
As you can see, I haven't gotten very far ^_^
Alright, I figured out a way of doing it that seems to be less then ideal... but it works so until someone provides a better solution I'll be stuck doing it like this:
dropit: function (node, data, dropRec, dropPosition) {
if (node.dragData.records[0].store.$className == "AM.store.BacklogCards")
{
data.records[0].set('state_id', 1);
this.getBacklogCardsStore().sync();
}
else if (node.dragData.records[0].store.$className == "AM.store.InprogressCards")
{
data.records[0].set('state_id', 2);
this.getInprogressCardsStore().sync();
}
else if (node.dragData.records[0].store.$className == "AM.store.ReviewCards")
{
data.records[0].set('state_id', 3);
this.getReviewCardsStore().sync();
}
else
{
data.records[0].set('state_id', 4);
this.getDoneCardsStore().sync();
}
I noticed that node.dragData.records[0].store.$className points to defined store that is what the grid bases itself on.
Using the data.records[0].set('state_id', 1); sets the state_id for the row that was moved over and then finally, I call the sync function to update the db with the new row information.