Displaying content based on radio button selection - javascript

I’m pretty new to backbonejs and i’m trying to create a basic application.
The application is something like this:
I have 5 sections: A, B, C, D and E
Each section has 2 radio buttons.
Section A - Radio1, Radio2
Section B - Radio3, Radio4
Section C - Radio5, Radio6
Section D - Radio7, Radio8
Section E - Radio9, Radio10
Depending on what radio button is selected, I need to display a section (previous sections must also display)
I have had a look at maybe using a model to determine which radio was selected and also what section is displayed. Is this the correct approach?

var State = Backbone.Model.extend({
defaults: {
id: null,
name: "",
isOn: false
}
});
var Section = Backbone.View.extend({
model: State,
events: {
'change [type="checkbox"]': function (event) {
var $checkbox = $(event.target);
this.model.set("isOn", $checkbox.is(":checked"));
this.model.get("dispatcher").trigger("toggle", this.model.get("id"));
}
},
initialize: function (options) {
this.listenTo(this.model, "change:isOn", function (model, isOn) {
if ( isOn ) {
this.$el.find(".section").show();
this.$el.find("input").prop("checked", true);
}
else {
this.$el.find(".section").hide();
this.$el.find("input").prop("checked", false);
}
});
this.listenTo(dispatcher, "toggle", function (id) {
if ( this.model.get("id") < id ) {
this.model.set("isOn", true);
}
if ( this.model.get("id") > id ) {
this.model.set("isOn", false);
}
});
},
render: function () {
this.$el.html('<div>' + this.model.get("name") + '</div><input type="checkbox"><div class="section" style="min-height:100px; background-color:grey; display:none;"></div>');
this.$el.appendTo("body");
}
});
var dispatcher = _.extend({}, Backbone.Events);
_.each([
{id: 1, name: "A", isOn: false, dispatcher: dispatcher},
{id: 2, name: "B", isOn: false, dispatcher: dispatcher},
{id: 3, name: "C", isOn: false, dispatcher: dispatcher},
{id: 4, name: "D", isOn: false, dispatcher: dispatcher},
{id: 5, name: "E", isOn: false, dispatcher: dispatcher}
], function (item) {
var view = new Section({model: new State(item)});
view.render();
});
I didn't understand the meaning of two radio.. so I used checkbox per section.. Hope this will uncover some basics of backbone.

Yes this approach will work. I recommend if you need to communicate between views to use models via events - this will generally result in better architecture (your views will be more decoupled).
You can react to the change event in the view (using the events hash) and update an attribute on a model for each group, e.g. this.model.set('termsAccepted', true/false) then as long as the other view(s) have access to this model you can react to the change event of that attribute, e.g. this.listenTo(this.model, 'change:termsAccepted', this.onTermsAcceptedChange).

There may be a very simple solution to your objective. If you monitor the radio-button state via a javascript function, then you can use that function to change a class statement for the parent div. In CSS, you can then define actions to hide or display a div based on it's class. This would only take a few lines of javascript and a few lines of CSS.
For example, this example in codepen shows a way to accomplish the hide/show for a variably sized div. The number of divs in this approach is arbitrary - you can have as many or few as you like, and the only action required of the user is to click on the header to expand or collapse the associated div. In this example, clicking on the header acts as a toggle to expand or collapse the associated div. The example is set up so that only one div is expanded at a time and clicking on a different header automatically collapses any open div so that only one div is open at a time. If you do not want that behavior, just remove the for loop in the accOff() function.

It's kinda like a choose-your-own-adventure, ya sipher_z?
I advise using the Backbone Router with route parameters storing the current state, that is, which section is currently showing.
Each component of the view should be a Backbone.View.extend({...}). Components might be Section and Radio.
On each Radio button, in HTML, put a data-go-to attribute, with a value of the section to go to next. Then, in your RadioView code, put a click event. When clicked, extract this data-go-to, and do something like location.hash = '/section/' + section to trigger your router.
Then, all your router does is hide all the Sections except the selected one whenever triggered. If there's no selection, it just shows the first one!
I'm not 100% sure of this strategy, but this is definitely "the Backbone way". Let me know if I can clear anything up.

Related

Retain the selection in previous page when moved to next in ExtJS Combo box

I have the ExtJs form combo which shows the values in the dropdown as checkbox and the value to select. I have used the pagination to list all the values with no of pages. I need the selected value to be retained in the current page even when we move to next or previous page and comes back to the same page(without selecting any thing in prev, next pages).
Code:
Ext.create('Ext.form.ComboBox', {
id: 'ageComboPickerReport',
name: 'ageComboPickerReport',
maxHeight: 150,
margin: '0 5 0 0',
width: 150,
emptyText: "Select tags",
listConfig: {
getInnerTpl: function (displayField) {
return '<div class="x-combo-list-item"><img src="" class="chkCombo-default-icon
chkCombo" /> {' + displayField + '}</div>';
},
autoEl: { 'q-name': 'rpt-age-criteria-list'
}, labelAlign: 'top',
pageSize: 25,
displayField: 'age', valueField: 'id', forceSelection: true, store: me.ageStore,
//Disable Typing and Open Combo
onFocus: function () {
if (!this.isExpanded) {
me.ageStore.load()
this.expand()
} this.getPicker().focus();
}
}),
Could any one tell me how to retain the selected value in the page when move to other page and comes back
I faced a very similar issue, in my case I had a grid with checkbox selection model, and I wanted to retain the selected rows during moving between pages. I am quite sure (although not 100%) that there is no built-in functionality for that in ExtJS. I can tell what I did, hope it helps, although you have to adopt it because it is not for ComboBox, but for Grid.
Since this is a paged store, not all of the records are loaded, only the ones that are currently on the displayed page. (I assume your store is remote because I see you call load on it.) So to achieve what you like, you need to:
keep track of the selected record ids to be able the reset selection on page,
keep track the items (records) themselves as well, since you will likely need them,
after a page is loaded and displayed, set the selection accordingly.
(This solution can have issues, so you need to be careful. Depending on the use case, it is possible for example that the user selects something, goes to another page and come back, but the previously selected row is no more available (it was deleted by someone else). You have to consider whether it affects you.)
The complete code is complicated and not general enough to share it, but I can outline the steps I did:
Set up two data entries in viewmodel to track the selected record ids and items (records):
data: {
multiSelection: {
ids: {},
items: [],
},
}
Add listeners to the grid's select and deselect events in the view:
listeners: {
select: 'onGridSelect',
deselect: 'onGridDeselect',
},
Create onGridSelect and onGridDeselect functions in the controller, and also add a isDataChanged variable to the controller to indicate whether the store was changed (it is changed on each paging). It is important because I will programatically change the selection, and I don't want my listeners to be executed in this case, only when the user interacts. Like this:
isDataChanged: false,
onGridSelect: function(selModel, record, index, eOpts) {
if (isDataChanged) {
return;
}
const viewModel = this.getViewModel(),
multiSelection = viewModel.get('multiSelection');
multiSelection.ids[record.getId()] = true;
Ext.Array.push(multiSelection.items, record);
},
onGridDeselect: function(selModel, record, index, eOpts) {
if (isDataChanged) {
return;
}
const viewModel = this.getViewModel(),
multiSelection = viewModel.get('multiSelection');
delete multiSelection.ids[record.getId()];
Ext.Array.remove(multiSelection.items, record);
},
Finally, add a listeners to the store to detect changes, this will be called every time the user moves between pages. This is a little tricky, because I need to access the grid from the store listeners which is not very ExtJS like, but I had to (store needs to be the grid's store:
store.on('datachanged', function(store, eOpts) {
// this part you have to figure out, my solution is way too specific
// for share
const grid = ...;
// this was important for me, if the store is really changed,
// deleted, added rows etc., I deleted the selection, but
// you can consider it
if (store.getUpdatedRecords().length > 0 ||
store.getRemovedRecords().length > 0 ||
store.getNewRecords().length > 0) {
// access the `viewmodel` and reset `multiSelection` data entries to
// default, I `return` here to skip the rest
return;
}
// disable listener
grid.getController().isDataChanged = true;
const selModel = grid.getSelectionModel(),
multiSelection = grid.getViewModel().get('multiSelection');
// deselect everything
selModel.deselectAll();
// get an array of the saved selection and find out, which
// record from the current page is in the saved multiSelection
const selected = [];
Ext.Array.each(grid.getStore().getRange(), function(record) {
if (multiSelection.ids[record.getId()]) {
Ext.Array.push(selected, record);
}
});
// apply selection to current page
selModel.select(selected);
// enable listener
grid.getController().isDataChanged = false;
}

add a new empty line in ui-grid

I'm trying to add a new empty line in ui-grid. I've tried looking in different tuto and example, but all that I found didn't reply to my spec, and I wasn't able to adapt it to what I'm looking for.
In fact I'm looking how to add a new empty line in an existing ui-grid neither using a button outside the grid nor a button in the rowfooter.
I'm looking to add a abutton in the grid like the + button shown in the screen shot below
or may be render automatically a new empty line when the rendering the ui-grid and a new one when all rows were filled.
I tried doing that using function in cell template but it's not working.
any help is really appreciated
The first option sounds like more of a CSS issue to me. Essentially, the add button would use some sort of font library containing a +, and you would need to position it in the corner of the grid. Perhaps looking at the grid footer would be a starting place. It sounds like you've seen the basics of creating an add row button here: http://ui-grid.info/docs/#/tutorial/112_swapping_data
The second option (render automatically a new empty line when the rendering the ui-grid and a new one when all rows were filled) requires a JavaScript approach.
The basic logic I followed is:
(Assume) Some data loads from somewhere in a backend (in this sample, it's a simulated load returning a promise as $http or $resource would).
After that data is loaded, we append a new row. We wait for the data first; otherwise we'd not be pushing the new row to the correct location.
Upon completion of the edit action, we set a timeout to ensure subsequent edits on other cells do not keep firing a new row. If the timeout is reached, we append a new row. If a subsequent edit action occurs, and a timeout promise exists (for adding a new row), we cancel it. Once no edit actions occur, and the timeout is reached, we push the new row.
To ensure that we are only taking action when our "extra row" is modified, when we create a row, a reference is maintained to the current row such that we can evaluate whether or not a received event is of interest (var newRowTimeoutPromise).
The core logic in code is below, with a sample implementation in Plnkr:
var extraRow = null,
addNewTimeoutMillis = 2000,
newRowTimeoutPromise = null;
loadData().then(function(data) {
$scope.gridOpts.data = data;
}).finally(function() {
// add initial empty row, and set our reference to it
extraRow = addEmptyRow($scope.gridOpts.data);
})
$scope.gridOpts = {
showGridFooter: true,
onRegisterApi: function(gridApi) {
$scope.gridApi = gridApi;
// listen for cell edit completion
gridApi.edit.on.afterCellEdit($scope, function(rowEntity, colDef, newValue, oldValue) {
// test if the edited row was the "extra row"
// otherwise, and edit to any row would fire a new row
// Set a timeout so we don't create a new row if the user has
// not finished their edit(s) on other fields
newRowTimeoutPromise = $timeout(function() {
if (rowEntity == extraRow) {
// add a new empty row, and set our reference to it
extraRow = addEmptyRow($scope.gridOpts.data);
newRowTimeoutPromise = null;
}
}, addNewTimeoutMillis);
})
// Listen for cell edit start, and cancel if we have a pending new
// row add. Otherwise, each time you finish the edit on a cell,
// this will fire.
gridApi.edit.on.beginCellEdit($scope, function(rowEntity, colDef, newValue, oldValue) {
if (newRowTimeoutPromise != null) {
$timeout.cancel(newRowTimeoutPromise);
}
})
}
};
http://plnkr.co/edit/IMisQEHlaZDCmCSpmnMZ?p=preview
I used jQuery to fetch and change style of specific cell elements of the cell template.
Here is a helpful Fiddle
Here is the controller script : -
var app = angular.module('app', ['ngTouch', 'ui.grid']);
app.controller('MainCtrl', ['$scope',
function($scope) {
$scope.gridOptions = {};
$scope.Add = function() {
$scope.gridOptions.data.push( { firstName: ' ',lastName:'',company:'' });
$(".ui-grid-coluiGrid").prevObject["0"].activeElement.style.display="none";
$(".ui-grid-cell")[$scope.gridOptions.data.length-2].style.display="inline";
};
$scope.gridOptions.onRegisterApi = registerGridApi;
function registerGridApi(gridApi) {
$scope.gridApi= gridApi
};
$scope.gridOptions.columnDefs = [{
name: 'firstName',
field: 'firstName',
}, {
name: 'lastNamer',
field: 'firstName'
}, {
name: 'ShowScope',
cellTemplate: '<button id="btb" ng-click="grid.appScope.Add()">+</button>'
}];
$scope.gridOptions.data = [{ yourdata}];
}
]);
To make it work properly 2 more things have to be done
Use cellContentEditable to make the rows editable
In order to disable display style of cell template button that appears on cells corresponding to rows of already existing data,you could use angular foreach or a for loop to iterate through these rows and disable style(I tried using renderContainers but it always returns the length of rendered rows outside Add functions as 0).
I have a working plunker over here.
http://plnkr.co/edit/Vnn4K5DcCdiercc22Vry?p=preview
In columnDefs, I have defined a separate column for add:
{
name: 'add',
displayName: '',
enableCellEdit: false,
enableColumnMenu: false,
width: '3%',
cellTemplate: '<div class="ui-grid-cell-contents" ng-click="grid.appScope.addRow()"><span ng-click="grid.appScope.addRow()">Add</span></div>'
}
And
$scope.addRow= function(){
var newlist = {"remarks":'',"testName":''};
$scope.gridOptions.data.push(newlist);
}
Update: A second plunker with bootstrap icons for add/remove
http://plnkr.co/edit/FjsA2r?p=preview

How to prevent Row selection on custom elements click in UI-GRID

I'm facing a problem in using UI-GRId with row selection and custom cell elements:
The sample plunker is here : http://plnkr.co/edit/Ed6s6CGFGXyUzj2cyx2g?p=preview
$scope.gridOptions = { showGridFooter:true,enableRowSelection: true, enableRowHeaderSelection: false };
$scope.gridOptions.columnDefs = [
{ name: 'id' },
{ name: 'name'},
{ name: 'age', displayName: 'Age (not focusable)', allowCellFocus : false },
{ name: 'address.city' },
{ name:'address.pin',cellTemplate:'<select><option value="122002">122002</option><option value="122001">122001</option></select>'}];
You can see that on row click, the respective row gets selected, while if you tend to select the dropdown options implicitly the row selection event also gets fired, I want that on elements click like dropdown here the row selection event should not be triggered.
Pls guide.
Interesting question, haven't run into it yet, but I am sure it's only time before I do. I've created a plunk to demonstrate my solution.
Basically, what I have do is registered a watcher, as mentioned by AranS. From there, we have two objects to work with: the row, and the event that occured. Since the event object discloses which element was selected (clicked), we can identify if it was a DIV, or something else. In the event of the change in the select list, the value of evt.srcElement.tagName is 'SELECT'.
http://plnkr.co/edit/k2XhHr2QaD1sA5y2hcFd?p=preview
$scope.gridOptions.onRegisterApi = function( gridApi ) {
$scope.gridApi = gridApi;
gridApi.selection.on.rowSelectionChanged($scope,function(row,evt){
if (evt)
row.isSelected = (evt.srcElement.tagName === 'DIV');
});
};
ui-grid's API allows controlling row selection. Look at this answer from another thread: https://stackoverflow.com/a/33788459/5954939. Basically you can use the event rowSelectionChanged or the isRowSelectable. Let me know if you need an example.

Customising CKEditors Link Plugin

I am editing the link plugin to allow staff to select links to internal content.
I have managed to add another tab to the link plugin dialog with a text input with an onKeyup event. The idea is, when they type it will list the results below where they can select the link they want. Once selected I was just going to update the info tab with the url and protocol.
Here is my code sections from the existing link plugin:
....
....
//Should update info tab with value
function AddLink(txtLink)
{
var dialog = this.getDialog();
dialog.setValueOf('info', 'url', txtLink);
dialog.setValueOf('info', 'protocol', '');
}
//called when the user types in the search box. currently just uses text for basic testing
var searchBoxChanged = function ()
{
var dialog = this.getDialog();
var href = $(this).attr('href');
var txt = dialog.getValueOf('article', 'searchWords');
$('#searchResults').html("Test Title");
}
....
....
{
//Adds extra tab to the link plugin for custom link searching
id: 'article',
label: linkLang.article,
title: linkLang.article,
elements:
[
{
type: 'text',
id: 'searchWords',
label: linkLang.articleSearch,
style: 'height:40px',
size: 29,
onKeyUp: searchBoxChanged
},
{
type: 'html',
html: '<div id="searchResults">Please start tying to get results</div>'
}
]
}
....
....
At the moment I am just using some basic static data from the textbox. The link in creating on the page ok, but when it is clicked I get the error:
CRIPT5009: 'AddLink' is undefined
Can anyone shed some light on where I am going wrong?
In my experience, ["x" is undefined] errors quite often mean there's a syntax error or, often, something in the function does not evaluate to what you think it does.
Possibly, this.getDialog() is out of context so it doesn't return anything. Then, dialog.setValueOf() won't work.

ExtJS Change Event Listener failing to fire

I was asked to post this as a question on StackOverflow by http://twitter.com/jonathanjulian which was then retweeted by several other people. I already have an ugly solution, but am posting the original problem as requested.
So here's the back story. We have a massive database application that uses ExtJS exclusively for the client side view. We are using a GridPanel (Ext.grid.GridPanel) for the row view loaded from a remote store.
In each of our interfaces, we also have a FormPanel (Ext.form.FormPanel) displaying a form that allows a user to create or edit records from the GridPanel. The GridPanel columns are bound to the FormPanel form elements so that when a record is selected in the GridPanel, all of the values are populated in the form.
On each form, we have an input field for the table row ID (Primary Key) that is defined as such:
var editFormFields = [
{
fieldLabel: 'ID',
id: 'id_field',
name: 'id',
width: 100,
readOnly: true, // the user cannot change the ID, ever.
monitorValid: true
} /* other fields removed */
];
So, that is all fine and good. This works on all of our applications. When building a new interface, a requirement was made that we needed to use a third-party file storage API that provides an interface in the form of a small webpage that is loaded in an IFrame.
I placed the IFrame code inside of the html parameter of the FormPanel:
var editForm = new Ext.form.FormPanel({
html: '<div style="width:400px;"><iframe id="upload_iframe" src="no_upload.html" width="98%" height="300"></iframe></div>',
/* bunch of other parameters stripped for brevity */
});
So, whenever a user selects a record, I need to change the src attribute of the IFrame to the API URL of the service we are using. Something along the lines of http://uploadsite.com/upload?appname=whatever&id={$id_of_record_selected}
I initially went in to the id field (pasted above) and added a change listener.
var editFormFields = [
{
fieldLabel: 'ID',
id: 'id_field',
name: 'id',
width: 100,
readOnly: true, // the user cannot change the ID, ever.
monitorValid: true,
listeners: {
change: function(f,new_val) {
alert(new_val);
}
}
} /* other fields removed */
];
Nice and simple, except that it only worked when the user was focused on that form element. The rest of the time it failed to fire at all.
Frustrated that I was past a deadline and just needed it to work, I quickly implemented a decaying poller that checks the value. It's a horrible, ugly hack. But it works as expected.
I will paste my ugly dirty hack in an answer to this question.
"The GridPanel columns are bound to
the FormPanel form elements so that
when a record is selected in the
GridPanel, all of the values are
populated in the form."
As I understand it from the quote above, the rowclick event is what actually triggers the change to your form in the first place. To avoid polling, this could be the place to listen, and eventually raise to your custom change event.
Here is the ugly hack that I did to accomplish this problem:
var current_id_value = '';
var check_changes = function(offset) {
offset = offset || 100;
var id_value = document.getElementById('id_field').value || '';
if ( id_value && ( id_value != current_id_value ) ) {
current_id_value = id_value;
change_iframe(id_value);
} else {
offset = offset + 50;
if ( offset > 2500 ) {
offset = 2500;
}
setTimeout(function() { check_changes(offset); }, offset);
}
};
var change_iframe = function(id_value) {
if ( id_value ) {
document.getElementById('upload_iframe').src = 'http://api/upload.php?id=' + id_value;
} else {
document.getElementById('upload_iframe').src = 'no_upload.html';
}
setTimeout(function() { check_changes(100); }, 1500);
};
It's not pretty, but it works. All of the bosses are happy.
If you took a moment to read the source, you would see that the Ext.form.Field class only fires that change event in the onBlur function

Categories

Resources