jquery-jable: How to display a field as read-only in the edit form? - jquery-jtable

I have a table pre-populated with the company LAN IP addresses with fields for associated data, status, etc. The (jquery-)jtable fields collection is configured like this.
fields: {
id: { title: 'ID'},
ip: { title: 'IP address, edit: false }
more: { ... }
}
This works but the problem is that when the edit dialog pops up the user can't see the ip address of the record being edited as jtable's edit form doesn't show the field.
I've read through the documentation but can't see any way to display a field as read-only in the edit form. Any ideas?

You don't need to hack the jTable library asset, this just leads to pains when you want to update to a later version. All you need to do is create a custom input via the jTable field option "input", see an example field setup to accomplish what you need here:
JobId: {
title: 'JobId',
create: true,
edit: true,
list: true,
input: function (data) {
if (data.value) {
return '<input type="text" readonly class="jtable-input-readonly" name="JobId" value="' + data.value + '"/>';
} else {
//nothing to worry about here for your situation, data.value is undefined so the else is for the create/add new record user interaction, create is false for your usage so this else is not needed but shown just so you know when it would be entered
}
},
width: '5%',
visibility: 'hidden'
},
And simple style class:
.jtable-input-readonly{
background-color:lightgray;
}

I have simple solution:
formCreated: function (event, data)
{
if(data.formType=='edit') {
$('#Edit-ip').prop('readonly', true);
$('#Edit-ip').addClass('jtable-input-readonly');
}
},
For dropdown make other options disabled except the current one:
$('#Edit-country option:not(:selected)').attr('disabled', true);
And simple style class:
.jtable-input-readonly{
background-color:lightgray;
}

I had to hack jtable.js. Start around line 2427. Changed lines are marked with '*'.
//Do not create element for non-editable fields
if (field.edit == false) {
//Label hack part 1: Unless 'hidden' we want to show fields even though they can't be edited. Disable the 'continue'.
* //continue;
}
//Hidden field
if (field.type == 'hidden') {
$editForm.append(self._createInputForHidden(fieldName, fieldValue));
continue;
}
//Create a container div for this input field and add to form
var $fieldContainer = $('<div class="jtable-input-field-container"></div>').appendTo($editForm);
//Create a label for input
$fieldContainer.append(self._createInputLabelForRecordField(fieldName));
//Label hack part 2: Create a label containing the field value.
* if (field.edit == false) {
* $fieldContainer.append(self._myCreateLabelWithText(fieldValue));
* continue; //Label hack: Unless 'hidden' we want to show fields even though they can't be edited.
* }
//Create input element with it's current value
After _createInputLabelForRecordField add in this function (around line 1430):
/* Hack part 3: Creates label containing non-editable field value.
*************************************************************************/
_myCreateLabelWithText: function (txt) {
return $('<div />')
.addClass('jtable-input-label')
.html(txt);
},
With the Metro theme both the field name and value will be grey colour.
Be careful with your update script that you're passing back to. No value will be passed back for the //edit: false// fields so don't include them in your update query.

A more simple version for dropdowns
$('#Edit-country').prop('disabled',true);
No need to disable all the options :)

Related

How to make a text field link be opened in a new tab, in Velo?

In Wix, I have a text field in a repeater that is used for navigating to other dynamic pages. The link works, but there are two problems with that. First, I have to click two times, not double click, for functioning the link. Second, I want to make the text field act as a button link, I mean be able to right click on that and choose 'open in new tab'. How can I fix these problems in my code?
Here is the code
// Navigating to related dynaic page
import wixLocation from 'wix-location';
export function ndText_click(event) {
$w("#repeater1").onItemReady(($item, itemData, index) => {
$item("#nText").onClick((event) => {
let postTypeValue = itemData.pType
wixData.query("Collection1").eq("_id", itemData._id)
.find()
.then(results => {
let item = results.items[0];
let pIDValue = item.postId;
if (postTypeValue == "R") {
wixLocation.to('/re/' + postIDValue);
} else if (postTypeValue == "L") {
wixLocation.to('/lo/' + postIDValue);
}
})
});
})
}
I suggest trying to use a button instead of the text element. You can usually style the button so it looks the same as the text element you already have. Then instead of setting the onClick, try setting the button's link and target properties.

Is there a way to debounce rowclick in tabulator

I have both rowclick and rowdblclick handlers for a tabulator table, I'd like to debounce the rowclick handler so I don't get two rowclick's then a rowdblclick firing off whenever I dblclick on a row, is there a built-in method to do this? I'm aware that I can use rxjs and create a subject and debounce, but I would like to use a built in debounce if it exists.
I have a very similar issue - global row/cellClick also fire when column based cellClick fire.
My work around is to place e.stopImmediatePropagation() into the column based cellClick function. This also still allows the rowDblClick event to pass upwards/downwards etc (bubbling?). However, this is probably the reverse of what you need, unless you remove the need for a double click by putting in an event column?
var editIcon = function(cell, formatterParams, onRendered){ //plain text value
return "<i class='fa fa-edit'></i>";
};
var table = new Tabulator("#table", {
columns:[
{title:"Name", field:"name"}, //etc
{formatter:editIcon, headerSort:false, width:40, align:"center", cellClick:function(e, cell){
// do whatever
e.stopImmediatePropagation(); //prevent table wide rowClick() from also triggering
},
],
rowClick:function(e, row){
//all rows/cells will inherit this function, however the cell level cellClick function will take the first bite of the event before bubbling up to rowClick
},
});
Don't know if this helps, probably some more elegant way, but sort of works for me.
This is a standard JavaScript click event behaviour rather than anything specific to Tabulator
Any time you bind a click and double click handler the click handler will be triggered first.
I would suggest that you use a set timeout to detect if the double click has happened, you then make the double click event clear the timeout preventing the click action from happening:
var debounce = null; //hold debounce timer
var table = new Tabulator("#table", {
rowClick:function(e, row){
debounce = setTimeout(function(){
//do something
}, 100)
},
rowDblClick:function(e, row){
clearTimeout(debounce);
},
});
What I ended up doing in the end is using an EventEmitter and doing a .emit and passing the id from the row that was clicked on. Then in my pipe for my subscription to the EventEmitter I did a .distinct, eliminating the second click on the same row when double clicking.
export class XYZComponent implements AfterViewInit {
ngAfterViewInit(): void {
this.tabX = new Tabulator('#xyz', {
columns: [
// 1
{
title: 'clickable column',
field: 'X'
headerSort: false,
// visible: false,
width: '5rem',
cellDblClick: this.itemDblClick.bind(this),
cellClick: this.itemClick.bind(this),
},
//...
]
}
);
}
private itemClick(e, item) {
// both cells and rows have a getData function
this.onItemSelect(item.getData());
}
private itemDblClick(e, item) {
// both cells and rows have a getData function
this.onItemEdit(item.getData());
}
}
export class ABCComponent implements AfterViewInit {
ngAfterViewInit(): void {
this.selectItemSubject
.pipe(
takeWhile(() => this.active)
, distinctUntilChanged() // this will eliminate the second click
)
.subscribe(item => {
// load additional data for item
});
this.editItemSubject.pipe(
takeWhile(() => this.active)
)
.subscribe((item) => {
// do whatever to edit the item
});
}
}

React Native: Reach-Navigation and Pouch-DB - db.put not done before "refresh" callback is run

Relative newbie; forgive me if my etiquette and form here aren't great. I'm open to feedback.
I have used create-react-native-app to create an application using PouchDB (which I believe ultimately uses AsyncStorage) to store a list of "items" (basically).
Within a TabNavigator (main app) I have a StackNavigator ("List screen") for the relevant portion of the app. It looks to the DB and queries for the items and then I .map() over each returned record to generate custom ListView-like components dynamically. If there are no records, it alternately displays a prompt telling the user so. In either case, there is an "Add Item" TouchableOpacity that takes them to a screen where they an add a new item (for which they are taken to an "Add" screen).
When navigating back from the "Add" screen I'm using a pattern discussed quite a bit here on SO in which I've passed a "refresh" function as a navigation param. Once the user uses a button on the "Add" screen to "save" the changes, it then does a db.post() and adds them item, runs the "refresh" function on the "List screen" and then navigates back like so:
<TouchableOpacity
style={styles.myButton}
onPress={() => {
if (this.state.itemBrand == '') {
Alert.alert(
'Missing Information',
'Please be sure to select a Brand',
[
{text: 'OK', onPress: () =>
console.log('OK pressed on AddItemScreen')},
],
{ cancelable: false }
)
} else {
this.createItem();
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('ItemsScreen');
}
}
}
>
And all of this works fine. The "refresh" function (passed as onGoBack param) works fine... for this screen. The database is called with the query, the new entry is found and the components for the item renders up like a charm.
Each of the rendered ListItem-like components on the "List screen" contains a react-native-slideout with an "Edit" option. An onPress for these will send the user to an "Item Details" screen, and the selected item's _id from PouchDB is passed as a prop to the "Item Details" screen where loadItem() runs in componentDidMount and does a db.get(id) in the database module. Additional details are shown from a list of "events" property for that _id (which are objects, in an array) which render out into another bunch of ListItem-like components.
The problem arises when either choose to "Add" an event to the list for the item... or Delete it (using another function via [another] slideout for these items. There is a similar backward navigation, called in the same form as above after either of the two functions is called from the "Add Event" screen, this being the "Add" example:
async createEvent() {
var eventData = {
eventName: this.state.eventName.trim(),
eventSponsor: this.state.eventSponsor.trim(),
eventDate: this.state.eventDate,
eventJudge: this.state.eventJudge.trim(),
eventStandings: this.state.eventStandings.trim(),
eventPointsEarned: parseInt(this.state.eventPointsEarned.trim()),
};
var key = this.key;
var rev = this.rev;
await db.createEvent(key, rev, eventData);
}
which calls my "db_ops" module function:
exports.createEvent = function (id, rev, eventData) {
console.log('You called db.createEvent()');
db.get(id)
.then(function(doc) {
var arrWork = doc.events; //assign array of events to working variable
console.log('arrWork is first assigned: ' + arrWork);
arrWork.push(eventData);
console.log('then, arrWork was pushed and became: ' + arrWork);
var arrEvents = arrWork.sort((a,b)=>{
var dateA = new Date(a.eventDate), dateB = new Date(b.eventDate);
return b.eventDate - a.eventDate;
})
doc.events = arrEvents;
return db.put(doc);
})
.then((response) => {
console.log("db.createEvent() response was:\n" +
JSON.stringify(response));
})
.catch(function(err){
console.log("Error in db.createEvent():\n" + err);
});
}
After which the "Add Event" screen's button fires the above in similar sequence to the first, just before navigating back:
this.createEvent();
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('ItemsDetails');
The "refresh" function looks like so (also called in componentDidMount):
loadItem() {
console.log('Someone called loadItem() with this.itemID of ' + this.itemID);
var id = this.itemID;
let totalWon = 0;
db.loadItem(id)
.then((item) => {
console.log('[LOAD ITEM] got back data of:\n' + JSON.stringify(item));
this.setState({objItem: item, events: item.events});
if (this.state.events.length != 0) { this.setState({itemLoaded: true});
this.state.events.map(function(event) {
totalWon += parseInt(event.eventPointsEarned);
console.log('totalWon is ' + totalWon + ' with ' +
event.eventPointsEarned + ' having been added.');
});
};
this.setState({totalWon: totalWon});
})
.catch((err) => {
console.log('db.loadItem() error: ' + err);
this.setState({itemLoaded: false});
});
}
I'm at a loss for why the List Screen refreshes when I add an item... but not when I'm doing other async db operations with PouchDB in what I think is similar fashion to modify the object containing the "event" information and then heading back to the Item Details screen.
Am I screwing up with Promise chain someplace? Neglecting behavior of the StackNavigator when navigating deeper?
The only other difference being that I'm manipulating the array in the db function in the non-working case, whereas the others I'm merely creating/posting or deleting/removing the record, etc. before going back to update state on the prior screen.
Edit to add, as per comments, going back to "List screen" and the opening "Item Details" does pull the database data and correctly shows that the update was made.
Further checking I've done also revealed that the console.log in createEvent() to print the response to the db call isn't logging until after some of the other dynamic rendering methods are getting called on the "Item Details" screen. So it seems as though the prior screen is doing the get() that loadItem() calls before the Promise chain in createEvent() is resolving. Whether the larger issue is due to state management is still unclear -- though it would make sense in some respects -- to me as this could be happening regardless of whether I've called my onGoBack() function.
Edit/bump: I’ve tried to put async/await to use in various places in both the db_ops module on the db.get() and the component-side loadItem() which calls it. There’s something in the timing of these that just doesn’t jive and I am just totally stuck here. Aside from trying out redux (which I think is overkill in this particular case), any ideas?
There is nothing to do with PDB or navigation, it's about how you manage outer changes in your depending (already mounted in Navigator since they are in history - it's important to understand - so componentDidMount isn't enough) components. If you don't use global state redux-alike management (as I do) the only way to let know depending component that it should update is passing corresponding props and checking if they were changed.
Like so:
//root.js
refreshEvents = ()=> { //pass it to DeleteView via screenProps
this.setState({time2refreshEvents: +new Date()}) //pass time2refreshEvents to EventList via screenProps
}
//DeleteView.js
//delete button...
onPress={db.deleteThing(thingID).then(()=> this.props.screenProps.refreshEvents())}
//EventList.js
...
constructor(props) {
super(props);
this.state = {
events: [],
noEvents: false,
ready: false,
time2refreshEvents: this.props.screenProps.time2refreshEvents,
}
}
static getDerivedStateFromProps(nextProps, currentState) {
if (nextProps.screenProps.time2refreshEvents !== currentState.time2refreshEvents ) {
return {time2refreshEvents : nextProps.screenProps.time2refreshEvents }
} else {
return null
}
}
componentDidMount() {
this._getEvents()
}
componentDidUpdate(prevProps, prevState) {
if (this.state.time2refreshEvents !== prevState.time2refreshEvents) {
this._getEvents()
}
}
_getEvents = ()=> {
//do stuff querying db and updating your list with actual data
}

How to add a Multi Checkbox List to the editor of a Form-based Element?

I'm trying to build a custom Typeahead Form Element. I have predefined multiple datasets to draw autocomplete suggestions from. When users add my field to a form, I want them to be able to select one or more of these datasets through checkboxes.
Orchard does not seem to have any Shape like this out of the box. By looking at other Form Elements in Orchard.DynamicForms and the SelectList Shape defined in Orchard.Forms.Shapes.EditorShapes.cs, I was able to piece together this working code for the Driver:
public class TypeaheadFieldElementDriver : FormsElementDriver<TypeaheadField> {
private readonly ITokenizer _tokenizer;
private readonly IEnumerable<IDataSet> _dataSets;
public TypeaheadFieldElementDriver(IFormsBasedElementServices formsServices, ITokenizer tokenizer, IEnumerable<IDataSet> _dataSets)
: base(formsServices)
{
_tokenizer = tokenizer;
this._dataSets = _dataSets;
}
...
protected override void DescribeForm(DescribeContext context) {
context.Form("TypeaheadField", factory =>
{
var shape = (dynamic)factory;
var form = shape.Fieldset(
Id: "TypeaheadField",
_Value: shape.Textbox(
Id: "Value",
Name: "Value",
Title: "Value",
Classes: new[] { "text", "medium", "tokenized" },
Description: T("The value of this typeahead field.")),
_DataSetIds: shape.SelectList(
Id: "DataSetIds",
Name: "DataSetIds",
Title: "Remote Datasets",
Multiple: true,
Description: T("The remote datasets to fetch suggestions from.")));
foreach (var dataSet in _dataSets)
{
form._DataSetIds.Items.Add(new SelectListItem { Text = T(dataSet.Description).Text, Value = dataSet.Id });
}
return form;
});
...
}
}
This successfully renders a multiple select field and sort of works for my purposes but I'm not able to completely unselect every option because the one that was selected keeps getting posted. For this reason and just general ease of use, I would prefer to render a list of checkboxes instead but I'm not sure how to proceed.

Drupal 6: Working with Hidden Fields

I am working on an issue i'm having with hooking a field, setting the default value, and making it hidden. The problem is that it is taking the default value, but only submitting the first character of the value to the database.
//Here is how I'm doing it
$form['field_sr_account'] = array( '#type' => 'hidden', '#value' => '45');
I suppose there is something wrong with the way that I have structured my array, but I can't seem to get it. I found a post, http://drupal.org/node/59660 , where someone found a solution to only the first character being submitted
//Here is the format of the solution to the post - but it's not hidden
$form['field_sr_account'][0]['#default_value']['value'] = '45';
How can I add the hidden attribute to this?
Have you tried using #default_value insted of #value?
Also if you're trying to pass some data to the submit that will not be changed in the form you should use http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html#value .
The answer was actually to set the value and the hidden attribute separately, then set the value again in the submit handler using the following format.
I'm not sure if it's all necessary, I suppose I probably don't need to assign it in the form alter, but it works, so I'm going to leave it alone...
$form['#field_sr_account'] = $club;
$form['field_sr_account'] = array( '#type' => 'hidden','#value' => $club);
}
}
/*in submit handler, restore the value in the proper format*/
$form_state['values']['field_sr_account'] = array('0' => array('value' => $form['#field_sr_account']));
An interesting solution from http://drupal.org/node/257431#comment-2057358
CCK Hidden Fields
/**
* Implementation of hook_form_alter().
*/
function YourModuleName_form_alter(&$form, $form_state, $form_id) {
if (isset($form['type']) && isset($form['#node'])) {
### Make a CCK field becoming a hidden type field.
// ### Use this check to match node edit form for a particular content type.
if ($form_id === 'YourContentTypeName_node_form') {
$form['#after_build'] = array('_test_set_cck_field_to_hidden');
}
}
}
function _test_set_cck_field_to_hidden($form, &$form_state) {
$form['field_NameToBeHidden'][0]['value']['#type'] = 'hidden';
$form['field_NameToBeHidden'][0]['#value']['value'] = 'testValue';
return $form;
}

Resources