BigCommerce - Custom Shipping Method (RoyalMail Widget for Collect Location) - checkout

I would like to setup custom shipping option using RoyalMail collect widget so customer can go through widget and it would return address after customer selection. So we need to map those address fields with customer shipping address fields.
I have tried with checkout-js-sdk but it required considerable effort to create customm checkout from scratch. I am aiming to have a solution to map widget with BigCommerce shipping method using JavaScript or Angular Js.
window.language = {{ langJson 'optimized_checkout' }};
widgetConfigObj = {
viewingOptions:['Grid'],
royalMail: { getRMId: function () { return 'key' } },
deliveryPostCode: 'NG11DB',
defaultRadius: 10,
defaultNbrOfResults: 10,
onOpenWidget: function () {
//document.getElementById('checkout').disabled = true;
},
onLocationSelected : function(localCollect) {
console.log(location.organisationName);
},
onCancelSelection : function() {/*a call back in case of cancelation*/},
};
var stopMusicExt = document.getElementsByClassName('optimizedCheckout-form-checklist-item')[2];
stopMusicExt.onclick = localCollectSelected(widgetConfigObj);
</script>```

Related

PayPal Smart Payment Buttons - validate shipping address before payment?

PayPal allows adding a delivery address street without a number.
As far as I know, it is not possible to check this behavior during the order process before the payment is finished.
The order data is available in the onApprove function scope.
But that's too late, because the payment is finished then?
Is there a way to add address validation?
<script src="https://www.paypal.com/sdk/js?client-id=abc123&disable-funding=credit,card,sepa,giropay&currency=EUR"></script>
<script>
paypal.Buttons({
createOrder: function(data, actions) {
return fetch('/paypal/order/create/', {
method: 'post'
}).then(function(res) {
return res.json();
}).then(function(orderData) {
if (orderData.error)
{
return actions.reject();
}
return orderData.id;
});
},
onShippingChange: function(data, actions) {
if (data.shipping_address.country_code !== 'DE')
{
return actions.reject();
}
return actions.resolve();
},
onApprove: function(data, actions) {
return fetch('/paypal/order/' + data.orderID + '/capture/', {
method: 'post'
}).then(function(res)
{
return res.json();
}).then(function(orderData) {
let re = /[0-9]/;
// test
if (re.exec(orderData.payer.address.address_line_1) === null)
{
// cancle payment on this place is to late, because payment is finished
}
var errorDetail = Array.isArray(orderData.details) && orderData.details[0];
if (errorDetail && errorDetail.issue === 'INSTRUMENT_DECLINED') {
return actions.restart();
}
if (errorDetail)
{
return;
}
return fetch('/paypal/finish-order/' + data.orderID + '/',{
headers: {
'Content-Type' : 'application/json',
},
body: JSON.stringify(orderData),
method: 'post'
}).then(function(result){
let dat = result.json();
return dat;
}).then(function(data)
{
if (data.error)
{
actions.reject();
}
if (data.destination)
{
actions.redirect(data.destination)
}
});
});
},
funding: {
allowed: [ paypal.FUNDING.CARD ],
disallowed: [ paypal.FUNDING.CREDIT ]
},
style: {
size: 'medium',
height: 40,
label: 'checkout'
}
}).render('#ppbut');
</script>
When onApprove is called, the payment is not finished. The order still needs to be captured.
Before capture, you can show an order review step if desired (for the customer to verify a new total, for example, or select from available shipping methods). This step can be done by updating the DOM dynamically (a redirect can also work but is best avoided).
If the address is invalid, show an error message accordingly and give an option to restart; do not proceed with any capture. You do not need to void the order (there is no method), simply forget about that order id and never capture it.
You can patch the order to a new total as needed (automatically, or based on selections in the review step you add).
Finally, proceed with the capture as normal and show success.
If you do add a user review step after approval, you should also change the last button at PayPal to say "Continue" instead of the default "Pay Now" to match your behavior. This is only a text change, the behavior only changes based on what you do in onApprove, which must correspond. To make the text change, add an application_context object to the top level (not inside purchase_units) of your order creation request, in which you specify a user_action parameter with the value CONTINUE.

Order Now button does not work on Magento Checkout page

Magento 2.4 PHP 7.4
Clicking on the 'Order Now' button on the checkout page does not trigger any action. There are no errors on Console or in the Network tab.
Our setup uses the Amasty_CheckoutCore module on the front end to create a generic 'Order Now' button which will be triggered for the different types of Place order requests depending on the Payment methods.
By referring some online solutions I linked the Order Now button with the Place Order that will appear on UI. But that didn't work. Please find below the code for the same. I added the summary.js.
app\design\frontend\Vendor\Amasty_CheckoutCore\web\js\summary.js
require(
[
'jquery',
'ko',
'Magento_Checkout/js/view/summary',
'Magento_Checkout/js/model/step-navigator',
],
function(
$,
ko,
Component,
stepNavigator
) {
'use strict';
console.log("inside summary js");
return Component.extend({
isVisible: function () {
return stepNavigator.isProcessed('shipping');
},
initialize: function () {
$(function() {
$('body').on("click", '#place-order-trigger', function () {
$(".payment-method._active").find('.action.primary.checkout').trigger( 'click' );
});
});
var self = this;
this._super();
}
});
}
);

Mootools stop form submit method

I don't want to use an <input type=submit /> button to submit a form and I am instead using an <a> element. This is due to styling requirements. So I have this code:
myButton.addEvent('click', function() {
document.id('myForm').submit();
});
However, I have also written a class that improves and implements the placeholder attribute on inputs and textareas:
var FDPlaceholderText = new Class({
Implements: Events,
initialize: function() {
var _self = this;
var forms = document.getElements('form');
forms.each(function(form) { // All forms
var performInit = false;
var i = 0;
var ph = [];
form.getElements('input, textarea').each(function(el) { // Get form inputs and textareas
if (el.getProperty('placeholder') != null) { // Check for placeholder attribute
performInit = true;
ph[i] = _self.initPlaceholder(el); // Assign the placeholder replacement to the elements
}
i ++;
});
if (performInit) {
_self.clearOnSubmit(form, ph);
}
});
},
clearOnSubmit: function(form, ph) {
form.addEvent('submit', function(e) {
ph.each(function(el) {
if (el.value == el.defaultValue) {
el.value = '';
}
});
});
},
initPlaceholder: function(el) {
el.defaultValue = el.getProperty('placeholder');
el.value = el.getProperty('placeholder');
el.addEvents({
'focus': function() {
if (el.value == el.defaultValue) el.value = '';
},
'blur': function() {
if(el.value.clean() == ''){
el.value = el.defaultValue;
}
}
});
return el;
}
});
window.addEvent('domready', function() {
new FDPlaceholderText();
});
The above class works great if a form is submitted using an actual <input type=submit /> button: it listens for a submit and clears the inputs values if they are still the default ones therefore validating that they are essentially empty.
However, it seems that because I am submitting one of my forms by listening to a click event on an <a> tag the form.addEvent('submit', function(e) { isn't getting fired.
Any help is appreciated.
well you can change the click handler to fireEvent() instead of call the .submit() directly:
myButton.addEvent('click', function() {
document.id('myForm').fireEvent('submit');
});
keep in mind a couple of things (or more).
placeholder values to elements that lack placeholder= attribute is pointless
if you detect placeholder support, do so once and not on every element, it won't change suddenly midway through the loop. you can go something like var supportsPlaceholder = !!('placeholder' in document.createElement('input')); - remember, there is no need to do anything if the browser supports it and currently, near enough 60% do.
you can otherwise do !supportsPlaceholder && el.get('placeholder') && self.initPlaceholder(el); - which avoids checking attributes when no need
when the form is being submitted you really need to clear placeholder= values in older browser or validation for 'required' etc will fail. if validation still fails, you have to reinstate the placeholder, so you need a more flexible event pattern
avoid using direct references to object properties like el.value - use the accessors like el.get('value') instead (for 1.12 it's getProperty)
for more complex examples of how to deal with this in mootools, see my repo here: https://github.com/DimitarChristoff/mooPlaceholder
This is because the submit() method is not from MooTools but a native one.
Maybe you can use a <button type="submit"> for your styling requirements instead.

Handle selected event in autocomplete textbox using bootstrap Typeahead?

I want to run JavaScript function just after user select a value using autocomplete textbox bootstrap Typeahead.
I'm searching for something like selected event.
$('.typeahead').on('typeahead:selected', function(evt, item) {
// do what you want with the item here
})
$('.typeahead').typeahead({
updater: function(item) {
// do what you want with the item here
return item;
}
})
For an explanation of the way typeahead works for what you want to do here, taking the following code example:
HTML input field:
<input type="text" id="my-input-field" value="" />
JavaScript code block:
$('#my-input-field').typeahead({
source: function (query, process) {
return $.get('json-page.json', { query: query }, function (data) {
return process(data.options);
});
},
updater: function(item) {
myOwnFunction(item);
var $fld = $('#my-input-field');
return item;
}
})
Explanation:
Your input field is set as a typeahead field with the first line: $('#my-input-field').typeahead(
When text is entered, it fires the source: option to fetch the JSON list and display it to the user.
If a user clicks an item (or selects it with the cursor keys and enter), it then runs the updater: option. Note that it hasn't yet updated the text field with the selected value.
You can grab the selected item using the item variable and do what you want with it, e.g. myOwnFunction(item).
I've included an example of creating a reference to the input field itself $fld, in case you want to do something with it. Note that you can't reference the field using $(this).
You must then include the line return item; within the updater: option so the input field is actually updated with the item variable.
first time i've posted an answer on here (plenty of times I've found an answer here though), so here's my contribution, hope it helps. You should be able to detect a change - try this:
function bob(result) {
alert('hi bob, you typed: '+ result);
}
$('#myTypeAhead').change(function(){
var result = $(this).val()
//call your function here
bob(result);
});
According to their documentation, the proper way of handling selected event is by using this event handler:
$('#selector').on('typeahead:select', function(evt, item) {
console.log(evt)
console.log(item)
// Your Code Here
})
What worked for me is below:
$('#someinput').typeahead({
source: ['test1', 'test2'],
afterSelect: function (item) {
// do what is needed with item
//and then, for example ,focus on some other control
$("#someelementID").focus();
}
});
I created an extension that includes that feature.
https://github.com/tcrosen/twitter-bootstrap-typeahead
source: function (query, process) {
return $.get(
url,
{ query: query },
function (data) {
limit: 10,
data = $.parseJSON(data);
return process(data);
}
);
},
afterSelect: function(item) {
$("#divId").val(item.id);
$("#divId").val(item.name);
}
Fully working example with some tricks. Assuming you are searching for trademarks and you want to get the selected trademark Id.
In your view MVC,
#Html.TextBoxFor(model => model.TrademarkName, new { id = "txtTrademarkName", #class = "form-control",
autocomplete = "off", dataprovide = "typeahead" })
#Html.HiddenFor(model => model.TrademarkId, new { id = "hdnTrademarkId" })
Html
<input type="text" id="txtTrademarkName" autocomplete="off" dataprovide="typeahead" class="form-control" value="" maxlength="100" />
<input type="hidden" id="hdnTrademarkId" />
In your JQuery,
$(document).ready(function () {
var trademarksHashMap = {};
var lastTrademarkNameChosen = "";
$("#txtTrademarkName").typeahead({
source: function (queryValue, process) {
// Although you receive queryValue,
// but the value is not accurate in case of cutting (Ctrl + X) the text from the text box.
// So, get the value from the input itself.
queryValue = $("#txtTrademarkName").val();
queryValue = queryValue.trim();// Trim to ignore spaces.
// If no text is entered, set the hidden value of TrademarkId to null and return.
if (queryValue.length === 0) {
$("#hdnTrademarkId").val(null);
return 0;
}
// If the entered text is the last chosen text, no need to search again.
if (lastTrademarkNameChosen === queryValue) {
return 0;
}
// Set the trademarkId to null as the entered text, doesn't match anything.
$("#hdnTrademarkId").val(null);
var url = "/areaname/controllername/SearchTrademarks";
var params = { trademarkName: queryValue };
// Your get method should return a limited set (for example: 10 records) that starts with {{queryValue}}.
// Return a list (of length 10) of object {id, text}.
return $.get(url, params, function (data) {
// Keeps the current displayed items in popup.
var trademarks = [];
// Loop through and push to the array.
$.each(data, function (i, item) {
var itemToDisplay = item.text;
trademarksHashMap[itemToDisplay] = item;
trademarks.push(itemToDisplay);
});
// Process the details and the popup will be shown with the limited set of data returned.
process(trademarks);
});
},
updater: function (itemToDisplay) {
// The user selectes a value using the mouse, now get the trademark id by the selected text.
var selectedTrademarkId = parseInt(trademarksHashMap[itemToDisplay].value);
$("#hdnTrademarkId").val(selectedTrademarkId);
// Save the last chosen text to prevent searching if the text not changed.
lastTrademarkNameChosen = itemToDisplay;
// return the text to be displayed inside the textbox.
return itemToDisplay;
}
});
});

How to create custom ExtJS form field component?

I want to create custom ExtJS form field components using other ExtJS components in it (e.g. TreePanel). How can I do it most easily?
I've read docs of Ext.form.field.Base but I don't want to define field body by fieldSubTpl. I just want to write code which creates ExtJS components and maybe some other code which gets and sets values.
Update: Summarized purposes are the followings:
This new component should fit in the
form GUI as a field. It should have
label and the same alignment (label,
anchor) of other fields without need
of further hacking.
Possibly, I have
to write some getValue, setValue
logic. I'd rather embed it into this component than making separated code which copies things into further hidden form fields that I also have to manage.
To extend #RobAgar 's answer, following a really simple Date Time field that I wrote for ExtJS 3 and it's quickport that I made for ExtJS 4. The important thing is the use of the Ext.form.field.Field mixin. This mixin provides a common interface for the logical behavior and state of form fields, including:
Getter and setter methods for field values
Events and methods for tracking value and validity changes
Methods for triggering validation
This can be used for combining multiple fields and let act them as one. For a total custom fieldtype I recommend to extend Ext.form.field.Base
Here is the example that I mentioned above. It should shoe how easy this can be done even for something like a date object where we need to format the data within the getter and setter.
Ext.define('QWA.form.field.DateTime', {
extend: 'Ext.form.FieldContainer',
mixins: {
field: 'Ext.form.field.Field'
},
alias: 'widget.datetimefield',
layout: 'hbox',
width: 200,
height: 22,
combineErrors: true,
msgTarget: 'side',
submitFormat: 'c',
dateCfg: null,
timeCfg: null,
initComponent: function () {
var me = this;
if (!me.dateCfg) me.dateCfg = {};
if (!me.timeCfg) me.timeCfg = {};
me.buildField();
me.callParent();
me.dateField = me.down('datefield')
me.timeField = me.down('timefield')
me.initField();
},
//#private
buildField: function () {
var me = this;
me.items = [
Ext.apply({
xtype: 'datefield',
submitValue: false,
format: 'd.m.Y',
width: 100,
flex: 2
}, me.dateCfg),
Ext.apply({
xtype: 'timefield',
submitValue: false,
format: 'H:i',
width: 80,
flex: 1
}, me.timeCfg)]
},
getValue: function () {
var me = this,
value,
date = me.dateField.getSubmitValue(),
dateFormat = me.dateField.format,
time = me.timeField.getSubmitValue(),
timeFormat = me.timeField.format;
if (date) {
if (time) {
value = Ext.Date.parse(date + ' ' + time, me.getFormat());
} else {
value = me.dateField.getValue();
}
}
return value;
},
setValue: function (value) {
var me = this;
me.dateField.setValue(value);
me.timeField.setValue(value);
},
getSubmitData: function () {
var me = this,
data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
data = {},
value = me.getValue(),
data[me.getName()] = '' + value ? Ext.Date.format(value, me.submitFormat) : null;
}
return data;
},
getFormat: function () {
var me = this;
return (me.dateField.submitFormat || me.dateField.format) + " " + (me.timeField.submitFormat || me.timeField.format)
}
});
Now that's cool. The other day, I created a fiddle to answer another question before realizing I was off-topic. And here your are, finally bringing to my attention the question to my answer. Thanks!
So, here are the steps required in implementing a custom field from another component:
Creating the child component
Render the child component
Ensuring the child component is sized and resized correctly
Getting and setting value
Relaying events
Creating the child component
The first part, creating the component, is easy. There's nothing particular compared to creating a component for any other usage.
However, you must create the child in the parent field's initComponent method (and not at rendering time). This is because external code can legitimately expect that all dependent objects of a component are instantiated after initComponent (e.g. to add listeners to them).
Furthermore, you can be kind to yourself and create the child before calling the super method. If you create the child after the super method, you may get a call to your field's setValue method (see bellow) at a time when the child is not yet instantiated.
initComponent: function() {
this.childComponent = Ext.create(...);
this.callParent(arguments);
}
As you see, I am creating a single component, which is what you'll want in most case. But you can also want to go fancy and compose multiple child components. In this case, I think it would be clever to back to well known territories as quickly as possible: that is, create one container as the child component, and compose in it.
Rendering
Then comes the question of rendering. At first I considered using fieldSubTpl to render a container div, and have the child component render itself in it. However, we don't need the template features in that case, so we can as well bypass it completely using the getSubTplMarkup method.
I explored other components in Ext to see how they manage the rendering of child components. I found a good example in BoundList and its paging toolbar (see the code). So, in order to obtain the child component's markup, we can use Ext.DomHelper.generateMarkup in combination with the child's getRenderTree method.
So, here's the implementation of getSubTplMarkup for our field:
getSubTplMarkup: function() {
// generateMarkup will append to the passed empty array and return it
var buffer = Ext.DomHelper.generateMarkup(this.childComponent.getRenderTree(), []);
// but we want to return a single string
return buffer.join('');
}
Now, that's not enough. The code of BoundList learns us that there's another important part in component rendering: calling the finishRender() method of the child component. Fortunately, our custom field will have its own finishRenderChildren method called just when that needs to be done.
finishRenderChildren: function() {
this.callParent(arguments);
this.childComponent.finishRender();
}
Resizing
Now our child will be rendered in the right place, but it will not respect its parent field size. That is especially annoying in the case of a form field, because that means it won't honor the anchor layout.
That's very straightforward to fix, we just need to resize the child when the parent field is resized. From my experience, this is something that was greatly improved since Ext3. Here, we just need to not forget the extra space for the label:
onResize: function(w, h) {
this.callParent(arguments);
this.childComponent.setSize(w - this.getLabelWidth(), h);
}
Handling value
This part will, of course, depend on your child component(s), and the field you're creating. Moreover, from now on, it's just a matter of using your child components in a regular way, so I won't detail this part too much.
A minima, you also need to implement the getValue and setValue methods of your field. That will make the getFieldValues method of the form work, and that will be enough to load/update records from the form.
To handle validation, you must implement getErrors. To polish this aspect, you may want to add a handful of CSS rules to visually represent the invalid state of your field.
Then, if you want your field to be usable in a form that will be submitted as an actual form (as opposed to with an AJAX request), you'll need getSubmitValue to return a value that can be casted to a string without damage.
Apart from that, as far as I know, you don't have to worry about the concept or raw value introduced by Ext.form.field.Base since that's only used to handle the representation of the value in an actual input element. With our Ext component as input, we're way off that road!
Events
Your last job will be to implement the events for your fields. You will probably want to fire the three events of Ext.form.field.Field, that is change, dirtychange and validitychange.
Again, the implementation will be very specific to the child component you use and, to be honest, I haven't explored this aspect too much. So I'll let you wire this for yourself.
My preliminary conclusion though, is that Ext.form.field.Field offers to do all the heavy lifting for you, provided that (1) you call checkChange when needed, and (2) isEqual implementation is working with your field's value format.
Example: TODO list field
Finally, here's a complete code example, using a grid to represent a TODO list field.
You can see it live on jsFiddle, where I tries to show that the field behaves in an orderly manner.
Ext.define('My.form.field.TodoList', {
// Extend from Ext.form.field.Base for all the label related business
extend: 'Ext.form.field.Base'
,alias: 'widget.todolist'
// --- Child component creation ---
,initComponent: function() {
// Create the component
// This is better to do it here in initComponent, because it is a legitimate
// expectationfor external code that all dependant objects are created after
// initComponent (to add listeners, etc.)
// I will use this.grid for semantical access (value), and this.childComponent
// for generic issues (rendering)
this.grid = this.childComponent = Ext.create('Ext.grid.Panel', {
hideHeaders: true
,columns: [{dataIndex: 'value', flex: 1}]
,store: {
fields: ['value']
,data: []
}
,height: this.height || 150
,width: this.width || 150
,tbar: [{
text: 'Add'
,scope: this
,handler: function() {
var value = prompt("Value?");
if (value !== null) {
this.grid.getStore().add({value: value});
}
}
},{
text: "Remove"
,itemId: 'removeButton'
,disabled: true // initial state
,scope: this
,handler: function() {
var grid = this.grid,
selModel = grid.getSelectionModel(),
store = grid.getStore();
store.remove(selModel.getSelection());
}
}]
,listeners: {
scope: this
,selectionchange: function(selModel, selection) {
var removeButton = this.grid.down('#removeButton');
removeButton.setDisabled(Ext.isEmpty(selection));
}
}
});
// field events
this.grid.store.on({
scope: this
,datachanged: this.checkChange
});
this.callParent(arguments);
}
// --- Rendering ---
// Generates the child component markup and let Ext.form.field.Base handle the rest
,getSubTplMarkup: function() {
// generateMarkup will append to the passed empty array and return it
var buffer = Ext.DomHelper.generateMarkup(this.childComponent.getRenderTree(), []);
// but we want to return a single string
return buffer.join('');
}
// Regular containers implements this method to call finishRender for each of their
// child, and we need to do the same for the component to display smoothly
,finishRenderChildren: function() {
this.callParent(arguments);
this.childComponent.finishRender();
}
// --- Resizing ---
// This is important for layout notably
,onResize: function(w, h) {
this.callParent(arguments);
this.childComponent.setSize(w - this.getLabelWidth(), h);
}
// --- Value handling ---
// This part will be specific to your component of course
,setValue: function(values) {
var data = [];
if (values) {
Ext.each(values, function(value) {
data.push({value: value});
});
}
this.grid.getStore().loadData(data);
}
,getValue: function() {
var data = [];
this.grid.getStore().each(function(record) {
data.push(record.get('value'));
});
return data;
}
,getSubmitValue: function() {
return this.getValue().join(',');
}
});
Heh. After posting the bounty I found out that Ext.form.FieldContainer isn't just a field container, but a fully fledged component container, so there is a simple solution.
All you need to do is extend FieldContainer, overriding initComponent to add the child components, and implement setValue, getValue and the validation methods as appropriate for your value data type.
Here's an example with a grid whose value is a list of name/value pair objects:
Ext.define('MyApp.widget.MyGridField', {
extend: 'Ext.form.FieldContainer',
alias: 'widget.mygridfield',
layout: 'fit',
initComponent: function()
{
this.callParent(arguments);
this.valueGrid = Ext.widget({
xtype: 'grid',
store: Ext.create('Ext.data.JsonStore', {
fields: ['name', 'value'],
data: this.value
}),
columns: [
{
text: 'Name',
dataIndex: 'name',
flex: 3
},
{
text: 'Value',
dataIndex: 'value',
flex: 1
}
]
});
this.add(this.valueGrid);
},
setValue: function(value)
{
this.valueGrid.getStore().loadData(value);
},
getValue: function()
{
// left as an exercise for the reader :P
}
});
I've done this a few times. Here is the general process/pseudo-code I use:
Create an extension of field that provides the most useful re-use (typically Ext.form.TextField if you just want to get/set a string value)
In the afterrender of the field, hide the textfield, and create a wrapping element around this.el with this.wrap = this.resizeEl = this.positionEl = this.el.wrap()
Render any components to this.wrap (e.g. using renderTo: this.wrap in the config)
Override getValue and setValue to talk to the component(s) you rendered manually
You may need to do some manually sizing in a resize listener if your form's layout changes
Don't forget to cleanup any components you create in the beforeDestroy method!
I can't wait to switch our codebase to ExtJS 4, where these kinds of things are easy.
Good luck!
Since the question was asked rather vague - I only can provide the basic pattern for ExtJS v4.
Even if it's not too specific, it has the advance that it's rather universal like this:
Ext.define('app.view.form.field.CustomField', {
extend: 'Ext.form.field.Base',
requires: [
/* require further components */
],
/* custom configs & callbacks */
getValue: function(v){
/* override function getValue() */
},
setValue: function(v){
/* override function setValue() */
},
getSubTplData: [
/* most likely needs to be overridden */
],
initComponent: function(){
/* further code on event initComponent */
this.callParent(arguments);
}
});
The file /ext/src/form/field/Base.js provides the names of all configs and functions that can be overridden.
Following the documentation at http://docs.sencha.com/ext-js/4-0/#/api/Ext.form.field.Base
This code will create a reusable TypeAhead/Autocomplete style field for selecting a language.
var langs = Ext.create( 'Ext.data.store', {
fields: [ 'label', 'code' ],
data: [
{ code: 'eng', label: 'English' },
{ code: 'ger', label: 'German' },
{ code: 'chi', label: 'Chinese' },
{ code: 'ukr', label: 'Ukranian' },
{ code: 'rus', label: 'Russian' }
]
} );
Ext.define( 'Ext.form.LangSelector', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.LangSelector',
allowBlank: false,
hideTrigger: true,
width: 225,
displayField: 'label',
valueField: 'code',
forceSelection: true,
minChars: 1,
store: langs
} );
You can use the field in a form simply by setting the xtype to the widget name:
{
xtype: 'LangSelector'
fieldLabel: 'Language',
name: 'lang'
}
Many of the answers either use the Mixin Ext.form.field.Field or just extends on some already made class that suits their needs - which is fine.
But I do not recommend fully overwriting the setValue method, that is IMO really bad form!
A lot more happens than just setting and getting the value, and if you fully overwrite it - well you will for instance mess up the dirty state, processing of rawValue etc..
Two options here I guess, one is to callParent(arguments) inside the method you declare to keep things streamlined, or to at the end when you are done apply the inherited method from where ever you got it (mixin or extend).
But do not just overwrite it with no regards for what that already made method does behind the scenes.
Also remember that if you use other field types in your new class - then do set the isFormField property to false - otherwise your getValues method on the form will take those values and run with em!
Another solution could be this tree-field implementation.
It behaves just like a normal form field:
https://github.com/wencywww/Ext.ux.form.field.Tree
Here is an example of a custom panel that extends an Ext Panel. You can extend any component, check the docs for the fields, methods and events you can play with.
Ext.ns('yournamespace');
yournamespace.MyPanel = function(config) {
yournamespace.MyPanel.superclass.constructor.call(this, config);
}
Ext.extend(yournamespace.MyPanel, Ext.Panel, {
myGlobalVariable : undefined,
constructor : function(config) {
yournamespace.MyPanel.superclass.constructor.apply(this, config);
},
initComponent : function() {
this.comboBox = new Ext.form.ComboBox({
fieldLabel: "MyCombo",
store: someStore,
displayField:'My Label',
typeAhead: true,
mode: 'local',
forceSelection: true,
triggerAction: 'all',
emptyText:'',
selectOnFocus:true,
tabIndex: 1,
width: 200
});
// configure the grid
Ext.apply(this, {
listeners: {
'activate': function(p) {
p.doLayout();
},
single:true
},
xtype:"form",
border: false,
layout:"absolute",
labelAlign:"top",
bodyStyle:"padding: 15px",
width: 350,
height: 75,
items:[{
xtype:"panel",
layout:"form",
x:"10",
y:"10",
labelAlign:"top",
border:false,
items:[this.comboBox]
},
{
xtype:"panel",
layout:"form",
x:"230",
y:"26",
labelAlign:"top",
border:false,
items:[{
xtype:'button',
handler: this.someAction.createDelegate(this),
text: 'Some Action'
}]
}]
}); // eo apply
yournamespace.MyPanel.superclass.initComponent.apply(this, arguments);
this.comboBox.on('select', function(combo, record, index) {
this.myGlobalVariable = record.get("something");
}, this);
}, // eo function initComponent
someAction : function() {
//do something
},
getMyGlobalVariable : function() {
return this.myGlobalVariable;
}
}); // eo extend
Ext.reg('mypanel', yournamespace.MyPanel);
Could you describe the UI requirements that you have a bit more? Are you sure that you even need to do build an entire field to support the TreePanel? Why not set the value of a hidden field (see the "hidden" xtype in the API) from a click handler on a normal tree panel?
To answer your question more fully, you can find many tutorials on how to extend ExtJS components. You do this by leveraging the Ext.override() or Ext.Extend() methods.
But my feeling is that you may be over-complicating your design. You can achieve what you need to do by setting a value to this hidden field. If you have complex data, you can set the value as some XML or JSON string.
EDIT Here's a few tutorials. I highly recommend going with the KISS rule when it comes to your UI design. Keep It Simple Stupid!
Extending components using panels