textbox textchanged event always firing - asp.net

I have a form with a few text-boxes and a Grid-view. There is this one text-box called "Inward Number", whose text-changed event fires all the time. When i want to fire another control's event, it simply fires the text-changed event of the Inward Number text-box. This happens with all of the other controls. Can someone please help?
protected void txtInward_No_TextChanged(object sender, EventArgs e)
{
a = 'A';
NewRow();
}
I have used the correct names, but still this error occurs.
<asp:TextBox ID="txtInward_No" runat="server" Width="220px" CssClass="txtbox"
EnableViewState="False"
AutoPostBack="True" ontextchanged="txtInward_No_TextChanged">
</asp:TextBox>
new row just makes a new row in my grid.
private void NewRow()
{
int rowIndex = 0;
if (ViewState["CurrentTable"] != null)
{
DataTable dtnew = new DataTable();
dtnew = (DataTable)ViewState["CurrentTable"];
DataRow drr = null;
if (dtnew.Rows.Count > 0)
{
for (int i = 1; i <= dtnew.Rows.Count; i++)
{
TextBox box1 = (TextBox)GridView1.Rows[rowIndex].Cells[2].FindControl("TextBox1");
TextBox box2 = (TextBox)GridView1.Rows[rowIndex].Cells[3].FindControl("TextBox2");
TextBox box3 = (TextBox)GridView1.Rows[rowIndex].Cells[4].FindControl("TextBox3");
TextBox box4 = (TextBox)GridView1.Rows[rowIndex].Cells[5].FindControl("TextBox4");
TextBox box5 = (TextBox)GridView1.Rows[rowIndex].Cells[6].FindControl("TextBox5");
drr = dtnew.NewRow();
if (txtInward_No.Text =="1")
{
drr["InwardNumber"] = txtInward_No.Text + b.ToString();
}
else
{
drr["InwardNumber"] = txtInward_No.Text + a.ToString();
}
drr["Bags"] = box1.Text;
drr["GrossWt"] = box2.Text;
drr["TareWt"] = box3.Text;
drr["NetWt"] = box4.Text;
drr["DCBillWt"] = box5.Text;
rowIndex++;
}
dtnew.Rows.Add(drr);
ViewState["CurrentTable"] = dtnew;
GridView1.DataSource = dtnew;
GridView1.DataBind();
}
}
PrevData();
b++;
a++;
}

http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.textbox.ontextchanged.aspx
A TextBox control must persist some values between posts to the server for this event to work correctly. Be sure that view state is enabled for this control.
Try enabling ViewState for TextBox.

I think what's probably happening is that ASP.NET relies on ViewState to decide whether the TextBox's value has changed. But you've disabled ViewState on this control. I'm not sure why it can't use ControlState instead, but you could try enabling ViewState for this TextBox and see if this has an effect.
The MSDN page on the OnTextChanged event mentions this.

Related

Capturing the value of dynamically created TextBox and DropDownList in ASP.NET

I am stuck in an issue, tried googling but could not find any solution.
I have created controls dynamically in Page_Load and I have a static button.
When the button is clicked, I need to capture user entry in those controls.
Now when I am trying to access the control using its unique id, error is being returned as the control is not available at runtime. :(
For loop - starts
if (dr["Type"].ToString().Trim() == "DropDown")
{
DropDownList ddl = new DropDownList();
ddl.Id = "ddl" + Convert.ToString(Counter);
ddl.DataTextField = "Text";
ddl.DataValueField = "Value";
ddl.DataSource = ds1;
ddl.DataBind();
ddl.EnableViewState = true;
cell.Controls.Add(ddl);
}
else if (dr["QuestionType"].ToString().Trim() == "TextBox")
{
TextBox txt = new TextBox();
txt.ID = "txt" + Convert.ToString(Counter);
txt.EnableViewState = true;
cell.Controls.Add(txt);
}
row.Cells.Add(cell);
table.Rows.Add(row);
Even if i do a FindControl in Btn_click function, it says that the panel has 0 controls.
var textbox = (TextBox)pnlMidTermFeedback.FindControl("txt5");
textbox is always NULL, although I have a control txt5. I can see it when I do a F12.
Please help.
Dynamically created controls must be recreated on every postback:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Set values and properties of controls specified in markup
...
}
// Create new controls and add them to the panel
...
}
They will then be available in the button event handler, and contain the data entered by the user.

How to determine which button is clicked in dynamically created DataTable

I'm creating dynamically data Table bound to Grid View. Every row is populated with button. When i determine which button is clicked on the row, i want to get the current value of cell in clicked button in that row and modify her.Help!
Markup
<asp:GridView ID="GridView2" runat="server"
OnRowDataBound="GridView2_RowDataBound">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="btnTest" runat="server"
CommandName="odzemi"
CssClass="button2"
Font-Bold="True"
Text="-"
Width="100px"
OnClick="btnTest_Click" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
//in this way a create row
private void AddNewRecordRowToGrid()
{
int counter;
if (Request.Cookies["kasa"] == null)
counter = 0;
else
{
counter = int.Parse(Request.Cookies["kasa"].Value);
}
counter++;
Response.Cookies["kasa"].Value = counter.ToString();
Response.Cookies["kasa"].Expires = DateTime.Now.AddYears(2);
if (ViewState["Markici"] != null)
{
DataTable dtCurrentTable = (DataTable)ViewState["Markici"];
DataRow drCurrentRow = null;
if (dtCurrentTable.Rows.Count > 0)
{
for (int i = 1; i <= dtCurrentTable.Rows.Count; i++)
{
HttpCookie cookie = Request.Cookies["Democookie"];
drCurrentRow = dtCurrentTable.NewRow();
drCurrentRow["FirmaID"] = Request.Cookies["firma"].Value;
drCurrentRow["Godina"] = Request.Cookies["godina"].Value;
drCurrentRow["KasaID"] = Request.Cookies["kasa"].Value;
drCurrentRow["MarkicaID"] = Request.Cookies["kasa"].Value;
drCurrentRow["Datum"] = DateTime.Now;
drCurrentRow["Masa"] = Session["masa39"];
drCurrentRow["VrabotenID"] = Session["New"];
drCurrentRow["Artikal"] = Label3.Text;
drCurrentRow["Cena1"] = Label4.Text;
//this is where i need to make changes
drCurrentRow["Kolicina"] = Label5.text;
drCurrentRow["Smena"] = Session["smena1"];
drCurrentRow["VkIznos"] = Label6.Text;
drCurrentRow["VkDanok"] = Label8.Text;
drCurrentRow["SySDatum"] = DateTime.Now;
drCurrentRow["Vid"] = Label23.Text;
drCurrentRow["Edmera"] = Label10.Text;
drCurrentRow["ArtikalID"] = Label33.Text;
}
//Removing initial blank row
if (dtCurrentTable.Rows[0][0].ToString() == "")
{
dtCurrentTable.Rows[0].Delete();
dtCurrentTable.AcceptChanges();
}
//Added New Record to the DataTable
dtCurrentTable.Rows.InsertAt(drCurrentRow,0);
//storing DataTable to ViewState
ViewState["Markici"] = dtCurrentTable;
//binding Gridview with New Row
GridView2.DataSource = dtCurrentTable;
GridView2.DataBind();
}
}
}
//in this button i call that method
protected void Button9_Click(object sender, EventArgs e)
{
AddNewRecordRowToGrid();
}
//
and here i'm determine clicked button of row, and here in this
dtCurrentTable.Rows[clickedIndex]["Kolicina"]= "" i need to
get cureent value of cell which is different in every row.
protected void btnTest_Click(object sender, EventArgs e)
{
DataTable dtCurrentTable = (DataTable)ViewState["Markici"];
var clickedRow = ((Button)sender).NamingContainer as GridViewRow;
var clickedIndex = clickedRow.RowIndex;
//so here i want to get current value of cell, and modify her
dtCurrentTable.Rows[clickedIndex]["Kolicina"] = " ";
GridView2.DataSource = dtCurrentTable;
GridView2.DataBind();
}
Try this.
protected void btnTest_Click(object sender, EventArgs e)
{
var clickedRow = ((Button)sender).NamingContainer as GridViewRow;
var clickedIndex = clickedRow.RowIndex;
}
Provided your GridView does not have sorting or paging enabled, from this clickedIndex, we can get the corresponding Row of the DataTable like this.
dtCurrentTable.Rows[clickedIndex]["Kolicina"] = "StackOverflow";
In the RowDataBound event, the EventArgument e should have the index of the current item bound in it. The code e.Row.RowIndex will return the index of the row in the Grid, not necessarily the datasource when you have paging applied. There is a 'DataItemIndex' property, another index that may give you what you are looking for, depending on what you are trying to do.
The grid also exposes the Rows collection, which you can use a foreach to loop through each one, and access that way too.
Note, the Rows on postback will only be based on the markup posted back through viewstate; the grid knows nothing about your data source (unless you rebind on every postback).

Creating dynamic controls in Page_PreRender based on viewstate causes button OnClick event to not work

I realize that dynamic controls should be created within Page_Load and Page_Init in order for them to be registered in the control tree.
I have created a custom control that requires the use of ViewState in a button OnClick event. This ViewState is then used to dynamically create controls.
Since the life-cycle will go: Page Load -> Button Click -> Page PreRender. The view-state will not be updated until "Button Click", thus I am creating my dynamic controls in Page PreRender. However, creating a button and programatically assigning the OnClick EventHandler in Page_PreRender does not work.
Does anyone know how I can get this to work?
btn_DeleteTableRow_Click will not fire. This is setup in CreatePartRows()
Here is my example:
<asp:UpdatePanel ID="up_RMAPart" runat="server" UpdateMode="Conditional" EnableViewState="true" ChildrenAsTriggers="true">
<ContentTemplate>
<div class="button" style="width: 54px; margin: 0px; float: right;">
<asp:Button ID="btn_AddPart" runat="server" Text="Add" OnClick="btn_AddPart_Click" />
</div>
<asp:Table ID="Table_Parts" runat="server" CssClass="hor-zebra">
</asp:Table>
<div class="clear"></div>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btn_AddPart" EventName="Click" />
</Triggers>
Code Behind:
[Serializable]
public struct Part
{
public string PartName;
public int Quantity;
public int PartID;
public Part(string sPartName, int iQuantity, int iPartID)
{
PartName = sPartName;
Quantity = iQuantity;
PartID = iPartID;
}
}
public partial class RMAPart : System.Web.UI.UserControl
{
private Dictionary<string,Part> m_RMAParts;
private int m_RowNumber = 0;
public Dictionary<string, Part> RMAParts
{
get
{
if (ViewState["m_RMAParts"] != null)
return (Dictionary<string, Part>)ViewState["m_RMAParts"];
else
return null;
}
set
{
ViewState["m_RMAParts"] = value;
}
}
public int RowNumber
{
get
{
if (ViewState["m_RowNumber"] != null)
return Convert.ToInt32(ViewState["m_RowNumber"]);
else
return 0;
}
set
{
ViewState["m_RowNumber"] = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
RMAParts = new Dictionary<string, Part>();
RowNumber = 0;
RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
RowNumber = 1;
CreatePartRows();
}
}
protected void Page_PreRender(object sender, EventArgs e)
{
CreatePartRows();
}
private void CreatePartRows()
{
Table_Parts.Controls.Clear();
TableHeaderRow thr = new TableHeaderRow();
TableHeaderCell thc1 = new TableHeaderCell();
thc1.Controls.Add(new LiteralControl("Part"));
thr.Cells.Add(thc1);
TableHeaderCell thc2 = new TableHeaderCell();
thc2.Controls.Add(new LiteralControl("Quantity"));
thr.Cells.Add(thc2);
TableHeaderCell thc3 = new TableHeaderCell();
thc3.Controls.Add(new LiteralControl(""));
thr.Cells.Add(thc3);
Table_Parts.Rows.Add(thr);
foreach (KeyValuePair<string, Part> kvp in RMAParts)
{
string[] sKey = kvp.Key.Split('_');
TableRow tr = new TableRow();
tr.ID = kvp.Key;
TableCell tc1 = new TableCell();
TextBox tb_Part = new TextBox();
tb_Part.ID = "tb_Part_" + sKey[1];
tb_Part.CssClass = "textbox1";
tc1.Controls.Add(tb_Part);
tr.Cells.Add(tc1);
TableCell tc2 = new TableCell();
TextBox tb_Quantity = new TextBox();
tb_Quantity.ID = "tb_Quanitty_" + sKey[1];
tb_Quantity.CssClass = "textbox1";
tc2.Controls.Add(tb_Quantity);
tr.Cells.Add(tc2);
TableCell tc3 = new TableCell();
Button btn_Delete = new Button();
btn_Delete.ID = "btn_Delete_" + sKey[1];
btn_Delete.CommandArgument = tr.ID;
btn_Delete.Click += new EventHandler(btn_DeleteTableRow_Click);
btn_Delete.Text = "Remove";
tc3.Controls.Add(btn_Delete);
tr.Cells.Add(tc3);
Table_Parts.Rows.Add(tr);
}
}
public void Reset()
{
Table_Parts.Controls.Clear();
RMAParts.Clear();
RowNumber = 0;
RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
RowNumber = 1;
CreatePartRows();
}
protected void btn_AddPart_Click(object sender, EventArgs e)
{
RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
RowNumber++;
}
protected void btn_DeleteTableRow_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
TableRow tr = (TableRow)Table_Parts.FindControl(btn.CommandArgument);
Table_Parts.Rows.Remove(tr);
RMAParts.Remove(btn.CommandArgument);
}
}
To ensure that the values of input fields persist across postbacks and that server events are raised:
Use view state to keep track of dynamically created controls.
Re-create the controls with the same IDs in LoadViewState (not Load or PreRender, because then the values of input fields will be lost).
The rest of this answer details how I modified your code to get it to work.
RMAPart.ascx
Just for convenience, you can declare the header row in the .ascx:
<asp:Table ID="Table_Parts" runat="server" CssClass="hor-zebra">
<asp:TableRow>
<asp:TableHeaderCell Text="Part" />
<asp:TableHeaderCell Text="Quantity" />
<asp:TableHeaderCell />
</asp:TableRow>
</asp:Table>
RMAPart.ascx.cs
To keep track of dynamically created rows, maintain a list of row IDs in view state:
public partial class RMAPart : System.Web.UI.UserControl
{
private List<string> RowIDs
{
get { return (List<string>)ViewState["m_RowIDs"]; }
set { ViewState["m_RowIDs"] = value; }
}
In the btn_AddPart_Click handler, generate a new row ID and create the controls for the new row:
protected void btn_AddPart_Click(object sender, EventArgs e)
{
string id = GenerateRowID();
RowIDs.Add(id);
CreatePartRow(id);
}
private string GenerateRowID()
{
int id = (int)ViewState["m_NextRowID"];
ViewState["m_NextRowID"] = id + 1;
return id.ToString();
}
private void CreatePartRow(string id)
{
TableRow tr = new TableRow();
tr.ID = id;
TableCell tc1 = new TableCell();
TextBox tb_Part = new TextBox();
tb_Part.ID = "tb_Part_" + id;
tb_Part.CssClass = "textbox1";
tc1.Controls.Add(tb_Part);
tr.Cells.Add(tc1);
TableCell tc2 = new TableCell();
TextBox tb_Quantity = new TextBox();
tb_Quantity.ID = "tb_Quantity_" + id;
tb_Quantity.CssClass = "textbox1";
tc2.Controls.Add(tb_Quantity);
tr.Cells.Add(tc2);
TableCell tc3 = new TableCell();
Button btn_Delete = new Button();
btn_Delete.ID = "btn_Delete_" + id;
btn_Delete.CommandArgument = id;
btn_Delete.Click += btn_DeleteTableRow_Click;
btn_Delete.Text = "Remove";
tc3.Controls.Add(btn_Delete);
tr.Cells.Add(tc3);
Table_Parts.Rows.Add(tr);
}
In the btn_DeleteTableRow_Click handler, delete the clicked row and update view state:
protected void btn_DeleteTableRow_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
TableRow tr = (TableRow)Table_Parts.FindControl(btn.CommandArgument);
Table_Parts.Rows.Remove(tr);
RowIDs.Remove(btn.CommandArgument);
}
Hook Page_Load and start things off by creating the first row:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Reset();
}
}
public void Reset()
{
while (Table_Parts.Rows.Count > 1)
Table_Parts.Rows.RemoveAt(Table_Parts.Rows.Count - 1);
ViewState["m_NextRowID"] = 0;
string id = GenerateRowID();
RowIDs = new List<string> { id };
CreatePartRow(id);
}
Override LoadViewState and re-create the rows using the IDs stored in view state:
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
foreach (string id in RowIDs)
{
CreatePartRow(id);
}
}
}
Dealing with Parts
The code above doesn't use your Part structure at all. To actually move data between your business objects and the user control, you can add a public method that takes a Part collection and uses it to create rows and populate text boxes, and then add another public method that reads out the values of the text boxes into a Part collection.
The button click isn't being fired because control events are called right after the Load event. Your button isn't in the control hierarchy at the time that the asp.net lifecycle is trying to call your event, so it's being dropped. Remember, it's a round-trip and the control has to exist on postback before the LoadComplete event fires for its event handlers to get called.
Create your dynamic controls in the PreLoad or Load event and you should be OK (you will have access to the full viewstate at that time to make any decisions regarding whether or not you need to dynamically create your delete button for that row).
ASP.net Page Lifecycle Docs: http://msdn.microsoft.com/en-us/library/ms178472(v=vs.100).aspx
I think that Robert has the right answer, but needs to be more clear about WHICH Page.Load he is talking about. There are three page requests here.
Initial Page request, no initial button click yet.
Postback on button click. No processing in Page Load. PreRender call creates the new table rows and the new button and links up the button click event to the new button.
Postback after the client clicks on the new button. You need to re-create the dynamic button in Page Load so that the Click event of the dynamic button doesn't get dropped.
Agree with Robert and Bill.
But to add here, in my opinion only way that you would achieve this is by creating a custom control/web server control (inheriting WebControl class), where you override the CreateChildControls method and RenderContents methods. I think this is what you mean when you said, in one of your comments, you are going to code out a grid-view version.

I have issue with viewstate of dropdown list [duplicate]

I have DropDownList that I have to populate in an event associated with a click event of another control.. the data is populated and present but when I select a value and postback the values are null. Which means the view state is not working.. Solutions say that populate the DropDown in Init() but I can't because the requirement does not allow this. I have to populate it on click event .. Viewstates are enabled.. The following code populates the DropDown..
if (e.CommandName == "Add Friend")
{
HtmlGenericControl divMySub = (HtmlGenericControl)GridViewUserSubjects.Rows[Convert.ToInt32(e.CommandArgument)].FindControl("divMySubjects");
divMySub.Style["display"] = "block";
DropDownList mySub = (DropDownList)GridViewUserSubjects.Rows[Convert.ToInt32(e.CommandArgument)].FindControl("DropDownListMySubjectz");
UpdatePanel mySubPanel = (UpdatePanel)GridViewUserSubjects.Rows[Convert.ToInt32(e.CommandArgument)].FindControl("UpdatePanelRequestAction");
DataView SubjectTableView = ProfileDataAccess.GetUserUnusedSubjectsForRequest(UserId ,RequesterId).DefaultView;
if (SubjectTableView.Count > 0)
{
mySub.DataSource = SubjectTableView;
mySub.DataTextField = "Name";
mySub.DataValueField = "Id";
mySub.DataBind();
}
else
{
divMySub.InnerText = "Requests Complete";
LinkButton buttonlink= (LinkButton)sender;
buttonlink.Enabled = false;
}
mySubPanel.Update();
}
and the following is the code for the postback that retrieves the value from the Dropdown list.. The DropDown is inside a Gridview Row.
protected void LinkButtonAddFriend_Command(object sender, CommandEventArgs e)
{
Guid RequestedId = new Guid(Membership.GetUser().ProviderUserKey.ToString());
Guid UserId = new Guid(HiddenFieldUserId.Value.ToString());
int UserSubjectId = Convert.ToInt32 (GridViewUserSubjects.DataKeys[Convert.ToInt32(e.CommandArgument)].Value);
DropDownList DDL = (DropDownList)GridViewUserSubjects.Rows[Convert.ToInt32(e.CommandArgument)].FindControl("DropDownListMySubjectz");
LinkButton RequestAction = (LinkButton)GridViewUserSubjects.Rows[Convert.ToInt32(e.CommandArgument)].FindControl("LinkButtonFriendAction");
int RequesterSubjectId = Convert.ToInt32(DDL.SelectedItem.Value);
if (FriendsDataAccess.InsertRequest(UserId, RequestedId, UserSubjectId, RequesterSubjectId))
{
RequestAction.Text = "Remove Request";
RequestAction.Enabled = true;
}
}
DDL is the Dropdown with trouble.
When are you databinding the GridView which is the container of the DropDownList? Do you have wrapped it in a if(!IsPostBack)-check? When you databind the GridView the child controls are always "resetted".
If you use a declarative databound control like ObjectDataSource you should avoid this.databind() if it's not necessary.

DropDownList-Items are null on postback

I have DropDownList that I have to populate in an event associated with a click event of another control.. the data is populated and present but when I select a value and postback the values are null. Which means the view state is not working.. Solutions say that populate the DropDown in Init() but I can't because the requirement does not allow this. I have to populate it on click event .. Viewstates are enabled.. The following code populates the DropDown..
if (e.CommandName == "Add Friend")
{
HtmlGenericControl divMySub = (HtmlGenericControl)GridViewUserSubjects.Rows[Convert.ToInt32(e.CommandArgument)].FindControl("divMySubjects");
divMySub.Style["display"] = "block";
DropDownList mySub = (DropDownList)GridViewUserSubjects.Rows[Convert.ToInt32(e.CommandArgument)].FindControl("DropDownListMySubjectz");
UpdatePanel mySubPanel = (UpdatePanel)GridViewUserSubjects.Rows[Convert.ToInt32(e.CommandArgument)].FindControl("UpdatePanelRequestAction");
DataView SubjectTableView = ProfileDataAccess.GetUserUnusedSubjectsForRequest(UserId ,RequesterId).DefaultView;
if (SubjectTableView.Count > 0)
{
mySub.DataSource = SubjectTableView;
mySub.DataTextField = "Name";
mySub.DataValueField = "Id";
mySub.DataBind();
}
else
{
divMySub.InnerText = "Requests Complete";
LinkButton buttonlink= (LinkButton)sender;
buttonlink.Enabled = false;
}
mySubPanel.Update();
}
and the following is the code for the postback that retrieves the value from the Dropdown list.. The DropDown is inside a Gridview Row.
protected void LinkButtonAddFriend_Command(object sender, CommandEventArgs e)
{
Guid RequestedId = new Guid(Membership.GetUser().ProviderUserKey.ToString());
Guid UserId = new Guid(HiddenFieldUserId.Value.ToString());
int UserSubjectId = Convert.ToInt32 (GridViewUserSubjects.DataKeys[Convert.ToInt32(e.CommandArgument)].Value);
DropDownList DDL = (DropDownList)GridViewUserSubjects.Rows[Convert.ToInt32(e.CommandArgument)].FindControl("DropDownListMySubjectz");
LinkButton RequestAction = (LinkButton)GridViewUserSubjects.Rows[Convert.ToInt32(e.CommandArgument)].FindControl("LinkButtonFriendAction");
int RequesterSubjectId = Convert.ToInt32(DDL.SelectedItem.Value);
if (FriendsDataAccess.InsertRequest(UserId, RequestedId, UserSubjectId, RequesterSubjectId))
{
RequestAction.Text = "Remove Request";
RequestAction.Enabled = true;
}
}
DDL is the Dropdown with trouble.
When are you databinding the GridView which is the container of the DropDownList? Do you have wrapped it in a if(!IsPostBack)-check? When you databind the GridView the child controls are always "resetted".
If you use a declarative databound control like ObjectDataSource you should avoid this.databind() if it's not necessary.

Resources