I'm trying to create a simple login window with the very common 'Remember me' functionality. The login validation is done AJAX style, thus the browser won't remember my input.
My approach is to use the built-in state functionality, but how to use it confuses me.
Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
}));
...
{
xtype: 'textfield',
fieldLabel: 'User name',
id: 'txt-username',
stateful: true,
stateId: 'username'
}, {
xtype: 'textfield',
fieldLabel: 'Password',
id: 'txt-password',
inputType: 'password',
stateful: true,
stateId: 'password'
}, {
xtype: 'button',
text: 'Validate',
stateEvents: 'click'
}
I know I have to implement the getState method, but on what component (my guess is on the two textfields)? Another thing I fail to realize is, how is my click event on the button connected to the state properties of my textfields?
Don't use state. You are storing the user's password in plain text in the browser's cookies. Anyone who has access to the browser can read it and it is being sent back to the server in every request.
Hopefully you are using some form of server-side sessions and are not depending on the user's authentication information being present in every request to maintain logged-in state. If so, then I recommend taking advantage of the password saving feature built in to most modern browsers to handle remembering of the user for the initial authentication in any given session.
For the browser's password saving feature to work the authentication form must be present in the document when the page is first loaded. Also, the credentials must be submitted by that form in a traditional (non-AJAX) submit which will refresh the entire page.
You can fulfill these requirements while still presenting the form in the ExtJS UI by initially rendering the form hidden into the document and then using the capabilities of ExtJS to commandeer existing HTML elements.
In the document's body put:
<form id="auth-form" action="/url/of/your/login/action" method="POST">
<input id="auth-username" type="text" name="username" class="x-hidden">
<input id="auth-password" type="password" name="password" class="x-hidden">
<input id="auth-submit" type="submit" class="x-hidden">
</form>
Then, in Ext.onReady or at the time you are displaying an authentication form build a panel which makes use of the above form elements:
new Ext.Panel({
el: 'auth-form',
autoShow: true,
layout: 'form',
items: [
{
xtype: 'textfield',
el: 'auth-username',
autoShow: true,
name: 'username',
fieldLabel: 'Username',
anchor: '100%'
},
{
xtype: 'textfield',
el: 'auth-password',
autoShow: true,
name: 'password',
fieldLabel: 'Password',
anchor: '100%'
}
],
buttons: [
{
text: 'Log in',
handler: function() {
Ext.get('auth-submit').dom.click();
}
}
]
});
The exact composition of the form may vary. It may be built into an Ext.Window instance or whatever else. What is important:
The username and password fields make use of the existing input fields through the 'el' and 'autoShow' config properties.
One of the panels containing the fields does the same for the existing form element.
The submission of the form is performed by a simulated click on the existing submit button.
Use with Ajax funcionality:
{
xtype: 'form',
autoEl: {
//normal post for false submit
tag: 'form',
action: "#",
method: 'post'
},
items: [
{
xtype: 'textfield',
name: 'username',
fieldLabel: 'Username',
listeners: {
afterrender:function(cmp){
cmp.inputEl.set({
autocomplete:'on'
});
}
}
},
{
xtype: 'textfield',
name: 'password',
inputType: 'password',
fieldLabel: 'Password',
listeners: {
afterrender:function(cmp){
cmp.inputEl.set({
autocomplete:'on'
});
},
}
},
{
xtype: 'button',
text: 'Login',
handler: function() {
Ext.Ajax.request(); //login ajax request
Ext.get('falsesubmit').dom.click(); //false submit
},
},
{
//button with submit input for false submit
xtype: 'button',
hidden:true,
listeners: {
afterrender: function() {
this.el.createChild({tag: 'input', type: 'submit', id: 'falsesubmit'});
}
}
}
]
}
This does not work with IE 8. A runtime error is produced. I don't know if it is because I am using Google Frame, but I would like to point out that el is one of the public properties not a config option so I don't believe that Ext was design to work like this. Also in Google Chrome you can select the username but the password does not display. I think this is part of the design of Google Chrome but I have also seen it work correctly on other sites with Google Chrome. I am not using AJAX to submit the form but I like the way the Ext textfields look and I like the Tool tips as well.
I don't see how this way is safer than using a cookie because now matter how you implement it the password is stored on the client machine. Tomorrow I am going to try a html5 client storage solution.
Letting the web browser control this functionally means that different users may have different experiences based on the browser they have access to (talking mainly in how google chrome handles saving passwords).
All in all a very good post thanks.
Related
I have an invoices table, which uses the Yajra Laravel data table. I want to filter data using 'created_at' column, which does exists in invoices table in the database but not in the table view.
Here is my datatable image:
And the code which take start and end dates:
$(function() {
$('#invoices_daterange').daterangepicker({
opens: 'left'
}, function(start, end, label) {
console.log("A new date selection was made: " + start.format('YYYY-MM-DD') + ' to ' + end.format('YYYY-MM-DD'));
});
});
Here is my datatabe JS code
$(function () {
let invoicedatatable = $('#invoicesdatable-table').DataTable({
pageLength: 100,
processing: true,
serverSide: true,
ajax: '{{ route('invoices.datatable') }}',
columns: [
{data: 'invoice_number', name: 'invoice_number'},
{data: 'partner', name: 'partner.full_name'},
{data: 'start', name: 'start'},
{data: 'end', name: 'end'},
{data: 'due', name: 'due'},
{data: 'actual_invoice_amount', name: 'actual_invoice_amount'},
{data: 'action', name: 'action', sortable: false, searchable: false},
],
lengthMenu: [
[10, 50, 100, 250, 3000, 5000],
[10, 50, 100, 250, 3000, 5000]
],
buttons: [{
extend: 'colvis',
text: '<i class="icon-three-bars"></i>',
className: 'btn bg-blue btn-icon dropdown-toggle'
}]
});
});
I did search and read most of the topics about it, but couldn't find anything to implement this.
What I want to do:
is to filter data using the 'created_at' column which is not in the view, but exists in my invoices table in the database.
How to do it?
I am not familiar with your datepicker widget, so I cannot use that in my example. But I think you should be able to adapt the following to use your datepicker.
In my example, I have two separate date fields ("from" and "to") in a form, with a "submit" button:
<div>
<form id="filter-form">
From:<input type="date" id="min-date" name="min-date">
To:<input type="date" id="max-date" name="max-date">
<input type="submit" value="Submit">
</form>
</div>
You don't need to use a form (I used a form here, because it is a simple demo).
In the page's script (the same place where the DataTable is defined), I add a "submit" function:
var url = '{{ route('invoices.datatable') }}';
$( "#filter-form" ).submit(function( event ) {
event.preventDefault();
invoicedatatable.ajax.url( url ).load();
});
I don't actually need to submit the form, so I disable the default submission using event.preventDefault();.
The line invoicedatatable.ajax.url( url ).load(); is explained below.
In my DataTable I change the basic Ajax call from this:
ajax: '{{ route('invoices.datatable') }}',
to this:
ajax: {
url: url,
type: "POST", // or 'GET' if you prefer
data: function (data) {
data.mindate = $('#min-date').val();
data.maxdate = $('#max-date').val();
}
},
This uses a DataTables function to manipulate the data option. This is the data which we will send to the server, as part of our server-side request.
I simply append two new variables to the existing data - mindate and maxdate. These contain the date range you need to use in the server, for filtering.
Note that the data variable passed into the function already contains some data, provided by DataTables for server-side processing. So, I am adding these two extra fields to that existing data.
The request sent from the browser to the server now looks like this. You can see mindate and maxdate at the bottom of the list:
{
"draw": "2",
"columns[0][data]": "id",
"columns[0][name]": "",
"columns[0][searchable]": "true",
"columns[0][orderable]": "true",
"columns[0][search][value]": "",
"columns[0][search][regex]": "false",
"columns[1][data]": "name",
"columns[1][name]": "",
"columns[1][searchable]": "true",
"columns[1][orderable]": "true",
"columns[1][search][value]": "",
"columns[1][search][regex]": "false",
... not all details shown
"order[0][column]": "0",
"order[0][dir]": "asc",
"start": "0",
"length": "10",
"search[value]": "",
"search[regex]": "false",
"mindate": "2021-06-08", // <--- mindate
"maxdate": "2021-06-16" // <--- maxdate
}
In the form submission event, there was this line:
invoicedatatable.ajax.url( url ).load();
This line causes the ajax call in the DataTable to be re-executed, and the table to be re-drawn. This is the trigger which causes the dates to be sent to the server, as part of a standard request. It's the same action as when a user clicks on a column to sort the data, or moves from one page to another page in the DataTable.
The server can process this request and extract the two date fields from the request, in the usual way. It can then use these values to filter the data, before building its response, to send back to the DataTable.
I have a page that allows users to edit a property listing they had previously submitted. I've been using bootstrap-fileinput to allow users to add images, and it will use the initialPreview attribute to show images that they've already uploaded. Users can remove the initialPreview images to remove images from the dropzone, but I can't find a way to pass this info to the server, that the user has removed these initialPreview images.
I've tried uploadExtraData: function() {}
But I can't get any information about the initialPreview images. Also, I am using the Laravel 5.7 PHP framework for my website.
<div class="form-group">
<label for="additional_info" class="col-lg-12 control-label">Add Photos to Attract Lender Interest</label>
<div class="col-lg-12">
<input type="file" name="image[]" id="image" multiple class="image" data-overwrite-initial="false"
data-min-file-count="0" value="{{ $mortgage->close_date}}">
</div>
</div>
{{-- Scripts for the pretty file input plugin called bootstrap-fileinput --}}
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-fileinput/4.4.7/js/fileinput.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-fileinput/4.5.2/themes/fas/theme.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" type="text/javascript"></script>
<script type="text/javascript">
$("#image").fileinput({
overwriteInitial: false,
initialPreview: [
// IMAGE DATA
"http://digitalbroker.test/storage/properties/847%20Queen%20Street%20West,%20Toronto,%20ON,%20Canada_1.JPG",
// IMAGE DATA
"http://digitalbroker.test/storage/properties/847%20Queen%20Street%20West,%20Toronto,%20ON,%20Canada_2.JPG",
],
initialPreviewAsData: true, // identify if you are sending preview data only and not the raw markup
initialPreviewFileType: 'image', // image is the default and can be overridden in config below
initialPreviewDownloadUrl: 'http://kartik-v.github.io/bootstrap-fileinput-samples/samples/{filename}', // includes the dynamic `filename` tag to be replaced for each config
showUpload: false,
theme: 'fas',
uploadUrl: "/submit-mortgage",
uploadExtraData: function () {
return {
_token: $("input[name='_token']").val(),
};
},
allowedFileExtensions: ['jpg', 'png', 'gif', 'jpeg'],
overwriteInitial: true,
showCaption: false,
showRemove: true,
maxFileSize: 5000,
maxFilesNum: 8,
fileActionSettings: {
showRemove: true,
showUpload: false,
showZoom: true,
showDrag: false,
},
slugCallback: function (filename) {
return filename.replace('(', '_').replace(']', '_');
}
});
</script>
Right now it just removes any old images upon submit and will save any newly uploaded ones. I'd like to both keep track of what initialPreview images were not removed, and which new images were uploaded.
I know this is an older question, but for those who stumble upon it here is a solution:
When a user clicks the remove button on the initialPreview frame you can pass information from that to the server by adding additional option to fileinput which will make an Ajax call each time the remove button is clicked.
Using the question above you would need to add:
initialPreviewConfig: [
{
// This is passed to the server in the request body as key: 0
key: 0,
// This is the url that you would send a POST request to that will handle the call.
url: 'http://www.example.com/image/remove',
// Any extra data that you would like to add to the POST request
extra: {
key: value
}
}
]
You would need to create an object for each item you have within your initialPreview array.
The OP's .fileinput would become:
$("#image").fileinput({
overwriteInitial: false,
initialPreview: [
// IMAGE DATA
"http://digitalbroker.test/storage/properties/847%20Queen%20Street%20West,%20Toronto,%20ON,%20Canada_1.JPG",
// IMAGE DATA
"http://digitalbroker.test/storage/properties/847%20Queen%20Street%20West,%20Toronto,%20ON,%20Canada_2.JPG",
],
initialPreviewConfig: [
{
key: 0,
url: '/image/remove', //custom URL
extra: {
image: '847 Queen Street West, Toronto, ON, Canada_1.JPG
}
},
{
key: 1,
url: '/image/remove', //custom URL
extra: {
image: 847 Queen Street West, Toronto, ON, Canada_2.JPG
}
},
],
initialPreviewAsData: true, // identify if you are sending preview data only and not the raw markup
initialPreviewFileType: 'image', // image is the default and can be overridden in config below
initialPreviewDownloadUrl: 'http://kartik-v.github.io/bootstrap-fileinput-samples/samples/{filename}', // includes the dynamic `filename` tag to be replaced for each config
showUpload: false,
theme: 'fas',
uploadUrl: "/submit-mortgage",
uploadExtraData: function () {
return {
_token: $("input[name='_token']").val(),
};
},
allowedFileExtensions: ['jpg', 'png', 'gif', 'jpeg'],
overwriteInitial: true,
showCaption: false,
showRemove: true,
maxFileSize: 5000,
maxFilesNum: 8,
fileActionSettings: {
showRemove: true,
showUpload: false,
showZoom: true,
showDrag: false,
},
slugCallback: function (filename) {
return filename.replace('(', '_').replace(']', '_');
}
});
I hope this helps anybody who comes across it.
FYI this is my first answer on SO (please be kind :P )
I have the following button within a Panel within a view:
items: [
{
xtype: 'button',
text: 'SEND',
ui: 'confirm',
docked: 'bottom',
handler: function(){
view.push('TouchNuts.view.Transactions',{
title: 'New views title',
html: 'Some content'
});
}
}
]
I would like to navigate to a page that consists of a list 'TouchNuts.view.Transactions' when I click it. Any ideas?
view needs to be a reference to your NavigationView. One way of getting that reference is by giving it an id and then using Ext.getCmp to fetch the component:
{
xtype: 'navigationview',
id: 'my-id',
items: [...]
}
Ext.getCmp('my-id');
I have a checkbox for each row within a kendo grid. If the user sorts or filters the grid, the checkmarks are cleared from the checkboxes. How can I prevent the checkboxes from unchecking or re-check them after the sort or filter occurs? Please refer to the following js fiddle to observe the behavior during sorting:
http://jsfiddle.net/e6shF/33/
Here's the code on the jsfiddle for reference (...needed to ask this question):
$('#grid').kendoGrid({
dataSource: { data: [{id:3, test:'row check box will unchecked upon sorting'}]},
sortable: true,
columns:[
{
field:'<input id="masterCheck" class="check" type="checkbox" /><label for="masterCheck"></label>',
template: '<input id="${id}" type="checkbox" />',
filterable: false,
width: 33,
sortable: false // may want to make this sortable later. will need to build a custom sorter.
},
{field: 'test',
sortable: true}
]});
basically the selection is cleared each time because the Grid is redrawn. You can store the check items in an array or object and when the Grid is redrawn (dataBound event) you can mark them again as checked.
To simplify things here is an updated version of you code. Also use the headerTemplate option to set header template - do not name your field like template instead.
var array = {};
$('#grid').kendoGrid({
dataSource: { data: [{id:3, test:'row check box will unchecked upon sorting'}]},
sortable: true,
dataBound:function(){
for(f in array){
if(array[f]){
$('#'+f).attr('checked','checked');
}
}
},
columns:[
{
headerTemplate:'<input id="masterCheck" class="check" type="checkbox" /><label for="masterCheck"></label>',
template: '<input id="${id}" type="checkbox" />',
filterable: false,
width: 33,
sortable: false // may want to make this sortable later. will need to build a custom sorter.
},
{field: 'test',
sortable: true}
]});
var grid = $('#grid').data().kendoGrid;
$('#grid tbody').on('click',':checkbox',function(){
var id = grid.dataItem($(this).closest('tr')).id;
if($(this).is(':checked')){
array[id] = true;
}else{
array[id] = false;
}
})
Link to the fiddle
If you are not too concerned about old browsers HTML5 storage might work for you
http://www.w3schools.com/html/html5_webstorage.asp
And of course jQuery comes with its own data storage capability.
I accidentally deleted this post before, so I am resubmitting :\
I'm new to Ext JS and MVC in general and am toying with creating an app with a chart nested within a panel nested within a border panel within an app. [From top to bottom it goes Viewport > bordered panel > panel in 'center region' > chart]
The reason why I'm nesting a panel within the border panel is that the nested panel will hold both the chart as well as a toolbar for the chart, both of which are dynamic depending on the user's selection.
While simply having the border panel reference the externally defined chart view works well, once I try having it reference an externally defined panel view it throws 'Uncaught TypeError: Cannot call method 'substring' of undefined', and Aptana gives me a 'name is undefined' namespace error whether or not I have the nested panel reference the chart or simply be left empty. I have double checked my name spacing so I'm a little lost in where to start looking for the problem.
My base application file is as follows:
Ext.application({
name: 'Chart',
appFolder: 'chart',
controllers:['sidebar.Navigation', 'commoditycontrol.Commoditycontrol',
'chart.oil.Spreads'],
launch: function() {
Ext.create('Ext.container.Viewport', {
layout: 'border',
items: [{
region: 'north',
xtype: 'commoditycontrol',
}, {
region: 'east',
xtype: 'sidebarnavigation',
}, {
region: 'center',
xtype: 'oilbase',
}]
});
},
});
The 'oilbase' view is simply a panel that imports the chart and chart toolbar view (in this case I've left the toolbar view out)
Ext.define('Chart.view.base.Oil', {
extend: 'Ext.panel.Panel',
alias: 'widget.oilbase',
name: 'oilbase',
layout: 'fit',
items: [{
xtype: 'oilspreads'
}]
});
And here's the chart view 'oilspreads'
Ext.define('Chart.view.chart.oil.Spreads', {
extend: 'Ext.chart.Chart',
alias: 'widget.oilspreads',
name: 'oilspreads',
layout: 'fit',
store: 'Chart.store.oil.Spreads',
config: {
style: {
background: '#333333'
},
},
axes: [
{
title: 'Close',
type: 'Numeric',
position: 'left',
fields: ['close'],
minimum: 0,
maximum: 100,
cls: 'axis'
},
{
title: 'Month',
type: 'Category',
position: 'bottom',
fields: ['month'],
cls: 'axis'
}
],
series: [
{
type: 'line',
xField: 'month',
yField: 'close'
}
]
});
Again, everything works fine if I reference the chart view in the application rather than the 'oilbase' empty panel. If I reference the default panel xtype, everything works as well.
Is nesting panels simply discouraged? My gut feeling is that I'm simply missing an obvious namespacing issue but I would appreciate a 2nd set of eyes, as well as comments as to my approach to the MVC pattern for ExtJs in general.
Thanks
For the view to be loaded correctly it has to be defined either in the views config of your app, or in the views config of one of the controllers.