The problem I'm having is that if a user clicks to edit a cell but then does something on another view that re-renders the BootstrapTable, the cell is still focused and retaining the old value until the user clicks somewhere else on the table.
I tried the following:
onBootstrapTableRef(instance) {
this.bootstrapTableRef = instance;
}
componentDidUpdate() {
this.bootstrapTableRef.reset(); //feel like either of these lines should do the job
this.bootstrapTableRef.cleanSelected();
}
public render() {
const cellEdit = {
mode: "click",
blurToSave: true
}
return (
<BootstrapTable data={this.props.settings} keyField="settingStage"
bordered={false} striped cellEdit={cellEdit} ref={this.onBootstrapTableRef}>
...
</BootstrapTable>
</div>
</div>
);
}
Related
I am sorting an array of so called 'activities' in my customElement using LitElement:
#property({ type: Array }) private activityListLocal: Array<Activity> = [];
in the parent customElement called "find-activity".
Each activity is being rendered here.
${repeat(
this.activityListLocal,
activity =>
html` <div class="activity-container">
<div class="activity">
<activity-info .activity=${activity}></activity-info>
</div>
<div class="activity" class="rating">
<activity-rating
.activity=${activity}
#appactivityremoveclick=${() => this.deleteActivity(activity)}
></activity-rating>
</div>
</div>`
)}
This is how it looks visually:
2 activities marked for some impression
On clicking the button "Highest Rating", I am sorting the list of activities:
sortActivityListLocal() {
this.activityListLocal = [...this.activityList];
this.activityListLocal = this.activityListLocal.sort((a, b) => (a.avgRating < b.avgRating ? 1 : -1));
}
if (category === 'all') {
this.activityListLocal = this.activityList;
} else if (category === 'Highest Rating') {
this.sortActivityListLocal();
if (this.activityListLocal.length === 0) {
this.nothingHere.style.display = 'block';
}
}
//....
}
Note: this.activityList is a local copy of the server response.
In the image, you see the two sliders, which should be updated to move with the activity if the position on the page changes. The issue: The "my rating" slider does not properly "move" with the activity, if it has been changed/dragged after the page has been loaded for the first time.
Before:
Activities are loaded in properly, cinema has a higher rating than Tennis
After:
Activities are sorted properly, all sliders are correctly "moved" if "myRating" has not been changed/dragged
But if the slider was dragged after inital load in, and then selecting the "highest rating" category and therefore sorting the array, it stays in place:
Before:
After loading
Dragging the slider (not even requesting an update with a click on the refresh icon, issue happening in both cases)
Modification leading to the issue
After:
Issue visible
The interesting thing, the slider has the correct! value in the html inspector, but the display is not showing it. Why is this happening?
Code of the component holding the sliders:
import { LitElement, html } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { httpClient } from '../../../http-client';
import { PageMixin } from '../../page.mixin';
import { Activity, Rating } from '../find-activity';
import componentStyle from './activity-rating.css';
#customElement('activity-rating')
// eslint-disable-next-line #typescript-eslint/no-unused-vars
class ActivityRatingComponent extends PageMixin(LitElement) {
static styles = componentStyle;
#property({ reflect: true }) activity = {} as Activity;
#property() rating = {} as Rating;
#query('#deleteButton') private deleteButton!: HTMLImageElement;
private currentSliderValue = -1;
async updated() {
console.log(
`Personal rating for ${this.activity.title} is ${this.activity.personalRating}, avgRating ${this.activity.avgRating}, currentSliderValue ${this.currentSliderValue}`
);
this.currentSliderValue = this.activity.personalRating ? this.activity.personalRating : 0;
console.log(`Current slider value after: ${this.currentSliderValue}`);
if (this.activity.deletepermission === false) this.deleteButton.style.display = 'none';
else this.deleteButton.style.display = 'inline';
}
render() {
return html`
${this.renderNotification()}
<div class="outer-rating">
<p>${this.activity.motivationtitle}</p>
<div class="slidecontainer">
<label for="overallRating">Overall Rating</label>
<input
type="range"
min="0"
max="100"
value=${this.activity.avgRating ? this.activity.avgRating : 0}
class="slider"
id="overallRating"
disabled
/>
</div>
<div class="slidecontainer">
<label for="myRating">My Rating</label>
<input
type="range"
min="0"
max="100"
value=${this.activity.personalRating ? this.activity.personalRating : '0'}
class="slider"
id="myRating"
#change="${(e: Event) => this.readcurrentSliderValue(e)}"
/>
<img id="personalSlider" src="/refresh.png" alt="update" #click=${this.savecurrentSliderValueToDb} />
<img
class="remove-task"
src="/deleteicon.png"
alt="update"
id="deleteButton"
#click="${this.confirmDelete}"
/>
</div>
</div>
`;
}
confirmDelete(e: Event) {
const target = e.target as HTMLInputElement;
if (target) {
const result = confirm('Want to delete?');
if (result) {
this.emit('appactivityremoveclick');
}
}
}
readcurrentSliderValue(e: Event) {
const target = e.target as HTMLInputElement;
if (e) {
this.currentSliderValue = Number(target?.value);
console.log('Read new slider value ' + Number(target?.value));
}
}
async savecurrentSliderValueToDb() {
const partialRating: Partial<Rating> = {
activityid: this.activity.id,
rating: Number(this.currentSliderValue) //userID is not included here as it is being provided by the auth Middleware on patch request.
};
await httpClient.patch(`rating/${this.activity.id}${location.search}`, partialRating);
const responseRatingAll = await httpClient.get(`rating/findAverageRating/${this.activity.id}` + location.search);
try {
this.activity.avgRating = (await responseRatingAll.json()).results;
this.activity.personalRating = partialRating.rating ? partialRating.rating : 0;
} catch (error) {
this.showNotification((error as Error).message, 'error');
}
this.requestUpdate();
}
emit(eventType: string, eventData = {}) {
const event = new CustomEvent(eventType, {
detail: eventData,
bubbles: true,
composed: true
});
this.dispatchEvent(event);
}
}
Visual confirmation that slider has the correct value, but doesn't show it.
Thank you :)
Edit: In addition to the answer below - specifically for the case where "you want to force a value to be set on an element". Lit has an optimization where "if a value hasn't changed, don't do anything". Rendering the same value to an expression will not cause the expression to update. To make sure Lit updates the expression if the underlying DOM value has changed use the live directive.
The native browser input elements default behavior is:
When the value attribute is changed, update the input elements value property.
After a manual user interaction (such as typing into the input element if it is a text input), the value attribute no longer updates the input property.
After the value property has been updated the attribute no longer causes the property to update.
Therefore by setting the value property the value updates.
Because of that browser behavior, in Lit you can use a property expression to set the value property.
I.e.: <input .value=${this.activity.avgRating ? this.activity.avgRating : 0}.
Below is an example of the browser input behavior. Click the two buttons. One will update the value attribute, the other the value property.
Then interact with the input. Type in it. Now the attribute button will stop working.
const inputEl = document.querySelector('input')
const getRandomValue = () => String(Math.random()).slice(0, 5)
document.querySelector("#btn-attr")
.addEventListener("click", () => {
inputEl.setAttribute('value', getRandomValue())
});
document.querySelector("#btn-prop")
.addEventListener("click", () => {
inputEl.value = getRandomValue()
});
<input value="12345">
<button id="btn-attr">Change input attribute</button>
<button id="btn-prop">Change input property</button>
I'm using react-router-dom and what I want is to be able to close a Modal when I click browser back button.
Also, in my scenario, the modal component is not the part of Switch. So how can I close the modal.
Thanks in advance. :)
You could probably use something like this to detect the press of the Back button.
componentDidUpdate() {
window.onpopstate = e => {
}
}
And then, depending on your modal (Bootstrap or something else) you can call .hide() or .close().
I've made a simple hook called useAppendLocationState that does all the job:
function SomeComponent() {
const [showModal , appendShowModal ] = useAppendLocationState('showModal');
return (
<div>
<div>...some view...</div>
<button onClick={() => appendOpenModal(true)}>open modal</button>
{showModal && <SomeModal closeHandler={() => window.history.back()} />}
</div>
)
}
useAppendLocationState returns a array with two entries just like useState, The first entry is the state prop value coming from browser location state and the second entry is a method that pushes a new item to browser history with new state prop appended to current location state.
here is our useAppendLocationState definition:
import { useHistory } from 'react-router';
export function useAppendLocationState(key) {
if (!key) throw new Error("key cannot be null or empty value")
const history = useHistory()
const currentLocationState = history.location.state;
const appendStateItemValue = (value) => {
const newLocationState = { ...currentLocationState }
newLocationState[key] = value;
history.push(history.location.pathname, newLocationState)
}
const stateItemValue = history.location.state && history.location.state[key]
return [stateItemValue, appendStateItemValue]
}
export default useAppendLocationState
Have you tried: ComponentWillUnmount?
I have a react component that I am using as checkpoint to check if the user has viewed a certain section of the site.
class ContentSection extends Component {
constructor(props) {
super(props);
}
render() {
const allParagraphs = [];
for (let i = 0; i < this.props.paragraphs.length; i++) {
let p = this.props.paragraphs[i];
allParagraphs.push(
<Paragraph key={i} image={p["img"]} text={p["text"]} />
);
}
return (
<div className="cs">
<ContentSectionCheckPointContainer
uniqueIndex={this.props.uniqueIndex}
/>
<h4 className="sectionTitle">THIS IS A SECTION!!!</h4>
{allParagraphs}
</div>
);
}
}
And this is the ContentSectionCheckPointContainer
const mapDispatchToProps = dispatch => {
return {
unlock: index => dispatch(Unlock_Index_Action(index))
};
};
const mapStateToProps = state => {
return {
CheckPoints: [...state.CheckPoints]
};
};
class ContentSectionCheckPoint extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.myRect = null;
this.checkVisibility = this.checkVisibility.bind(this);
}
componentDidMount() {
this.checkVisibility();
window.addEventListener('scroll', this.checkVisibility);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.checkVisibility);
}
checkVisibility() {
if (this.myRef.current) {
let rect = this.myRef.current.getBoundingClientRect();
var viewHeight = Math.max(
document.documentElement.clientHeight,
window.innerHeight
);
let b = !(rect.bottom < 0 || rect.top - viewHeight >= 0);
if (b !== this.props.CheckPoints[this.props.uniqueIndex]) {
if (b) {
this.props.unlock(this.props.uniqueIndex);
}else{
this.props.unlock(this.props.uniqueIndex);
}
}
}
}
render() {
this.checkVisibility();
return (
<div ref={this.myRef} className="cscp">
{this.props.CheckPoints[this.props.uniqueIndex] && <p>hi</p>}
</div>
);
}
}
const ContentSectionCheckPointContainer = connect(
mapStateToProps, mapDispatchToProps)(ContentSectionCheckPoint);
As you can see I ran a visibility check on scroll, which works fine. However, I wanted to also run the visibility check immediately when the page is loaded before any scrolling occur.
It is to my understanding that componentDidMount is when React already rendered an element for the first time, so I wanted to do the check then. However, I was trying to render two ContentSection components, each containing their own check point. The latter check point for unknown reason is positioned higher than it appears on screen during componentDidMount, resulting in my visibility check returning true even though it is not visible. If I refresh the page, its position is correct again and the visibility check is fine.
This problem only seem to occur during the first time when I open up a new tab to load my code, but no longer occurs after a refresh on that tab.
Any idea why?
I have one list in that on click of each item one class is toggle in that list. Here is my code
My Code
I am toggling class in list. I want to remove unselected element from list when user click on "ADD" button. I have done till toggle class in list but I am facing problem while removing items on basis of css class. Please help.
if i understood exactly what you need, that you want to delete the unselected items when clicking the button ADD,
then add this to your button.
<button (click)="removeUnSelected()">ADD</button>
and add this function to your app.ts file
removeUnSelected() {
console.log(this.items[0].active);
// check if enything is selected first
let flag=0;
let i=0;
for(i=0;i<this.items.length;i++) {
if(this.items[i].active) {
flag=1;
break;
}
}
if(flag){
for(i=0;i<this.items.length;i++) {
if(!this.items[i].active) {
this.items.splice(i, 1);
i--;
}
}
}
}
i tried it already... this will delete the items from the array if they are not selected when clicking the button.
Try this:
DEMO
deleteNotSelect() {
let data = this.items.filter(data => data.active == true)
if (data.length > 0) {
this.items = data;
}
}
Use a forEach loop to toggle false the active propery of the item:
add() {
this.items.forEach(function(v,i){
v.active = false;
})
}
demo
I recently took over the development of an AngularJS application created by my company, and am currently trying to add a button to a cell in a table, which the user will be able to use as a navigation button, and set the location it directs you to as any one of the user's custom pages.
In the table directive, I have added the following code:
.directive('appTable', function(fxTag) {
return {
...
template: '...',
controller: function($scope, $element, $compile, Global, fxEvent, fxSearch, NotifyMgr) {
...
var goToPageBtnTmpl = $compile(
'<a href="javascript:;" ng-click="goToPage(target)"' +
'<class="btn btn-xs btn-brand">Go to Page</a>'
);
console.log("goToPageBtnTmpl defined: ", goToPageBtnTmpl);
...
var goToPage = function(target) {
// Code to navigate to the page set by the user
console.log("goToPage button added: ");
};
...
}
}
})
and in ctrl.js, there is a toWidgetObj() function, which creates a widget based on the details that the user selects/ enters on a form:
}).controller('WidgetPickerCtrl', function($scope, $timeout, fxTag, gPresets, appWidget, appUI, pagesPresets) {
...
function toWidgetObj() {
...
var widgetObj = {
name: $scope.widget.name,
label: $scope.widget.label,
attr: angular.copy($scope.widget)
};
switch(widgetObj.name) {
case 'app-table':
...
angular.forEach(widgetObj.table.rows, function(row) {
if(row.length > 0) {
reducedRows.push(row.map(
function(tag) {
if(tag.isTag) {
return {tag: tag.tag, nounit: tag.nounit};
}
if(tag.isBtn) {
var goToPageBtnTmpl = $compile(
'Go to page'
)
}
}
));
}
});
...
break;
...
}
...
return widgetObj;
}
...
});
When I currently click the 'Edit widget' button on a table widget, the 'Edit Widget dialog is opened, and I add a button to a cell in the table. When I then click the 'Preview' button, to update the widget with the changes I have entered in the dialog, I see the print statement from the directive displayed:
goToPageBtnTmpl defined: publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){
assertArg(scope, 'scope');
and I am expecting the table to show a the button that has been compiled by the line:
var goToPageBtnTmpl = $compile(
'<a href="javascript:;" ng-click="goToPage(target)"' +
'class = "btn btn-xs btn-brand">Go to page</a>'
)
that will take the user to the page whose address I specified in the input on the dialog.
But what I actually see when I click 'Preview' is the table displayed, and the cell where the button should be displayed is actually showing the link that I typed (i.e. the address of the page I am expecting it to take the user to when clicked).
My debug in the console is stating that the button has been added successfully:
Tag is a button:
{tag: "pages/userpage1", isTag: false, isBtn: true, nounit: false, units: undefined}
isBtn
:
true
isTag
:
false
nounit
:
false
tag
:
"pages/userpage1"
units
:
undefined
but I don't actually see the button displayed on the page.
Anyone have any suggestions what I'm doing wrong here?
Edit
So, as I've looked into this further, I think I may have found the reason that the button is not displayed: the code where the button is compiled is written in the table directive's controller function:
.directive('appTable', function(fxTag) {
return {
...
controller: function(...) {
...
var gotToPageBtnTmpl = $compile(
...
);
So this is run when the page first loads. However, I am trying to add the button to the table manually, after the page has already loaded, and the code I'm using to do this is written later in the same controller function:
if(!$scope.noTagAlarm()) {
angular.forEach($scope.config.columns, function(col) {
...
if(col.header.startsWith(":")) {
angular.forEach($scope.config.rows, function(row) {
col.template = function(value, row, metadata) {
goToPageBtnTmpl(value);
}
})
}else{
console.log("column doesn't start with ':' ");
}
...
});
}
My thought is that since this code is written in the directive's controller function, it is probably only run when the page is first loaded, and not when I edit the widget using my 'edit widget' dialog, so the HTML is not rendered.
Would that be the case? If so, how can I reload the widget after editing it without refreshing the whole page? Or, if not, what am I actually doing wrong here?
The issue here was that the function used in the col.template needed to return the button I wanted to create:
col.template = function(value, row, metadata) {
return [
umWidget.getBtnTmpl(
$scope.$new(true),
goToPage,
value,
value)
];
}
I defined the getBtnTmpl() function in Widget/service.js with:
function getBtnTmpl(scope, fn, target, title) {
scope.fn = fn;
scope.target = target
var pageTitle = title.split('/');
scope.title = pageTitle[1];
return btnTmpl(scope);
}
and set btnTmpl as a global variable in Widget/service.js with:
var btnTmpl = $compile(
'<a href="javascript:;" ng-click="fn(target)"' +
'class="btn btn-xs btn-brand">{{title}}</a>'
);