i want to use the routing system to provide back-button support. i am showing a list of items based on a record store. when i handle the itemtap event function(list, index, target, record) i get a reference to the selected record which i pass to a new screen which i instantiate and show.
under this scenario, how do i instruct the history manager that i have moved to a new screen and enable the back button. i see application.redirectTo but i would lose my reference to the record i want to use. (redirectTo will push a new action into the history stack that includes the primary id of the record but that seems redundant as i would then need re-find the record based on the id when i handle the redirect.)
is there a direct way to tell the history object to advance to a new action and also enable back button support?
I didn't find how to do it easier. Here's how I do it in my application (with nested list).
I created controller with routing like this:
Ext.define('MyApp.controller.ListItems', {
extend: 'Ext.app.Controller',
config: {
refs: {
nestedList: '.nestedlist'
},
control: {
nestedList: {
itemtap: 'itemSelected'
}
},
routes: {
'item/:id': 'showListItem'
}
},
list: null,
index: null,
target: null,
record: null,
showListItem: function( id ){
var nestedList = this.getNestedList(),
store = nestedList.getStore(),
node = store.getById(id);
if ( node.isLeaf() ){
nestedList.goToLeaf(node);
nestedList.fireEvent('leafitemtap', nestedList, this.list, this.index, this.target, this.record);
} else {
nestedList.goToNode(node);
}
},
itemSelected: function( nl, list, index, target, record ){
this.list = list;
this.index = index;
this.target = target;
this.record = record;
var path = 'item/'+record.data.id;
this.redirectTo(path);
}
});
I've also overriden onItemTap method of nested list for all list changes now is done via controller & propper routing.
onItemTap: function(list, index, target, record, e){
this.fireEvent('itemtap', this, list, index, target, record, e);
}
Hope it'll be helpful.
Write here, if you find better way, please.
Related
I'm using ckEditor 5.
My aim is to create a simple search functionality to perform some ajax calls, allow the user to select from a result list and act accordingly (inserting link, or image, or media...).
The problem is that I want to dynamically update the plugin view. Something similar to this: https://github.com/ckeditor/ckeditor5/issues/520, but I'm using a dropdown in the toolbar (I started from the EmbedMedia plugin and didn't care too much about design for now).
I asked the same question on their git (https://github.com/ckeditor/ckeditor5/issues/1681)
My template is like:
this.setTemplate( {
tag: 'form',
attributes: {
class: [
'ck',
'ck-results-form'
],
tabindex: '-1'
},
children: [
this.searchInputView,
this.searchButtonView,
{
tag: 'div',
attributes: {
class: [ 'ck-result-grid' ]
},
children: this.items
},
this.saveButtonView,
this.cancelButtonView,
]
} );
// updateList method is like this:
updateList( valuesArray ) {
console.log(this.items);
this.items = new Array();
console.log(this.items);
for (let i = 0; i < valuesArray.length && i < 20; i++) {
const urlView = new TableSizeGridBoxView(i, valuesArray[i]);
urlView.on('select-url', () => {
this._selectedUrl = valuesArray[i];
});
this.items.push( urlView );
}
console.log(this.items);
//do something here to force refresh or render the section
}
I'm able to get
My expected result, after the search, is to get
and finally select the result.
Currently I can see the object correctly initialized and updated by doing console.log, but the view doesn't update... it stays blank as in the first image (that's why I think there must be something I should be able to put where I wrote //do something here to force refresh or render the section).
Furtermore I can tell that TableSizeGridBoxView is working properly because I'm able to display arbitrary values if I call the for loop in the initialization of the view.
I have created a dijit.Tree object where every node is a checkbox. When you select/deselect the parent node, the child nodes get selected/deselected;
when one of the children is deselected, the parent gets deselected; when all the children are selected, the parent gets selected. It works perfectly fine.
However I need it to be keyboard accessible. When I navigate to the tree nodes and press spacebar or Enter, nothing happens.
I tried adding tabindex and aria-role to the checkbox (programmatically), but it did not work.
Here is the fiddle - http://jsfiddle.net/pdabade/pyz9Lcpv/65/
require([
"dojo/_base/window", "dojo/store/Memory",
"dijit/tree/ObjectStoreModel",
"dijit/Tree", "dijit/form/CheckBox", "dojo/dom",
"dojo/domReady!"
], function(win, Memory, ObjectStoreModel, Tree, checkBox, dom) {
// Create test store, adding the getChildren() method required by ObjectStoreModel
var myStore = new Memory({
data: [{
id: 'allDocuments',
name: 'All Documents'
}, {
id: 'inboxDocuments',
name: 'Inbox Documents',
parent: 'allDocuments'
}, {
id: 'outboxDocuments',
name: 'Outbox Documents',
parent: 'allDocuments'
}, {
id: 'draftDocuments',
name: 'Draft Documents',
parent: 'allDocuments'
}, {
id: 'finalDocuments',
name: 'Final Documents',
parent: 'allDocuments'
}],
getChildren: function(object) {
return this.query({
parent: object.id
});
}
});
// Create the model
var myModel = new ObjectStoreModel({
store: myStore,
query: {
id: 'allDocuments'
}
});
// Create the Tree.
var tree = new Tree({
model: myModel,
autoExpand: true,
getIconClass: function(item, opened) {
// console.log('tree getIconClass', item, opened);
// console.log('tree item type', item.id);
},
onClick: function(item, node, event) {
//node._iconClass= "dijitFolderClosed";
//node.iconNode.className = "dijitFolderClosed";
var _this = this;
console.log(item.id);
var id = node.domNode.id,
isNodeSelected = node.checkBox.get('checked');
dojo.query('#' + id + ' .dijitCheckBox').forEach(function(node) {
dijit.getEnclosingWidget(node).set('checked', isNodeSelected);
});
if (item.id != 'allComments') {
if (!isNodeSelected) {
var parent = node.tree.rootNode; // parent node id
//console.log(node);
parent.checkBox.set('checked', false);
} else {
var parent = node.tree.rootNode;
var selected = true;
var i = 0;
dojo.query('#' + parent.id + '.dijitCheckBox').forEach(function(node) {
if (i > 0) {
var isSet = dijit.getEnclosingWidget(node).get('checked');
console.log(isSet);
if (isSet == false) {
selected = false;
}
}
i++;
});
if (selected) {
parent.checkBox.set('checked', true);
}
}
}
//console.log(node.id);
},
_createTreeNode: function(args) {
var tnode = new dijit._TreeNode(args);
tnode.labelNode.innerHTML = args.label;
console.log(args);
var cb = new dijit.form.CheckBox({
"aria-checked": "false",
"aria-describedby": args.label
});
cb.placeAt(tnode.labelNode, "first");
tnode.checkBox = cb;
return tnode;
}
});
tree.placeAt(contentHere);
tree.startup();
tree.checkedItems();
//tree.expandAll();
});
}
Any ideas as to how to make it keyboard accessible?
Thanks!
Looking into the dijit/Tree source I see that it sets the function _onNodePress() as an event handler for keyboard events. You can override it (or add an aspect after it) and handle the key presses you want manually. It takes as argument the tree node and an event object that you can use to check specifically for the space and the enter key.
I forked your jsfiddle with an example: https://jsfiddle.net/pgianna/jjore5sm/1/
_onNodePress: function(/*TreeNode*/ nodeWidget, /*Event*/ e){
// This is the original implementation of _onNodePress:
this.focusNode(nodeWidget);
// This requires "dojo/keys"
if (e.keyCode == keys.ENTER || e.keyCode == keys.SPACE)
{
var cb = nodeWidget.checkBox;
cb.set('checked', !cb.get('checked'));
}
}
Do not add role, aria-checked, nor tabindex to the checkbox. Those are already built into the control, so you are adding risk of breaking it down the road. You can probably also get rid of every role="presentation" as those are on <div>s and <span>s which are presentational by nature. Finally, you need <label> on each block of text that is associated with a checkbox if you want this to be accessible. The aria-describedby is incorrect and is the less good option anyway.
I am getting the error: Uncaught TypeError: tree.checkedItems is not a function (line 159)
You also have a big focus management problem. Put the following in your CSS and you will see that it takes two presses of the Tab key for each single control (if starting at a focused checkbox): :focus {outline:2px solid #f00;}
It looks like you have the containing elements stealing any clicks, meaning the correct element never gets selected. The <span> with classes dijit dijitReset dijitInline dijitCheckBox keeps stealing focus as it toggles its tabindex from -1 to 0, taking itself in and out of the tab order. That may be a factor.
I suggest addressing the script error and then looking at focus management.
With dijit, there's all kinds of stuff going on in the background that might be out of your control. As aardrian said, there's lots of role=presentation and all the aria tags on the <input type='checkbox> are superfluous. dijit is probably (incorrectly) setting all that. An <input type='checkbox> already handles selections and it's role is inherently a checkbox. Those aria properties are for when you're making a custom checkbox out of div/span tags.
There is a native checkbox buried down in the code but it has opacity set to 0 so you can't see it. dijit is probably using it for the checkbox events.
The native checkbox also has data-dojo-attach-event="ondijitclick:_onClick". I'm not sure what that means but anytime I see "click" in an event name, I get suspicious that it might not work with a keyboard.
I tried the example on https://dojotoolkit.org/reference-guide/1.10/dijit/form/CheckBox.html and it works with the keyboard. Hit space will check and uncheck the box. Whether you can see the focus on the checkbox is another issue.
As a side note, it might be nice if your checkbox tree used aria-checked="mixed" for the parent branch. Anytime you have child checkboxes where some are selected and some are not, you can use "mixed" for the parent checkbox to indicate a mixture of selections. Not sure if dijit supports that.
I have a grid within a window. The grid has 3 Actions: Edit, Delete and Disable.I was wondering if it is possible to make the text of the Disable Action (which is currently 'Disable/Enable') to be dependent on the Current Status of the record selected. So say the user selects a record whose Current Status is Enabled, then the action's text should be 'Disable'. If, however, the user selects a record whose status is Disabled, then the action's text should be 'Enable'. Is it possible to do this when using Action? Or do I need to use a button instead of Action?
I am assuming your action button is in a toolbar that is docked to the top of your grid panel. The only tricky thing is getting a reference to the grid (without hardcoding it). The grid's 'select' event only gives you a reference to the rowmodel used.
/* Set a action attribute on the Ext.Action so we can find it */
var action = new Ext.Action({
text: 'Do something',
handler: function(){
Ext.Msg.alert('Click', 'You did something.');
},
iconCls: 'do-something',
itemId: 'myAction',
action: 'myAction' // I don't like itemId's personally :)
});
/* In the Controller */
init: function() {
this.control({
'mygrid': {
select: this.onRecordSelect
}
});
},
onRecordSelect: function(rowModel, record) {
var grid = rowModel.views[0].ownerCt);
var action = grid.getDockedItems('toolbar[dock="top"]')[0].down('button[action="myAction"]');
var enabled = (record.get('CurrentStatus') == "Enabled");
action.setText(enabled ? 'Disable' : 'Enable');
action.setIconCls(enabled ? 'myDisableCls' : 'myEnableCls');
}
/* in SASS */
.myDisableCls{
background-image:url(#{$icon_path}/checkbox.png) !important;
}
.myEnableCls {
background-image:url(#{$icon_path}/checkbox_ticked.png) !important;
}
Good luck!
I solved the problem in another way. This is my code:
grid.getSelectionModel().on({
selectionchange: function(sm, selections) {
if (selections.length > 0) {
Edit.enable();
Delete.enable();
if(selections[0].data.CurrentStatus == "Disabled"){
Disable.setText("Enable");
Disable.enable();
}else{
Disable.setText("Disable");
Disable.enable();
}
} else {
Edit.disable();
Delete.disable();
Disable.disable();
}
}
});
I have a WinJS application with listviews in which if quickly navigate between pages before the listview is fully loaded, the next page shows the listview with all elements in it bound as "undefined".
So say I have a hub page with a "to do" that is filtered to only show 6 items, and there is a header that navigates to the full "to do" page, when the hub page is displayed but before it is fully loaded I click on the header link to the "to do" page, the app then goes to the "to do" page, but the items show up with all the properties in the tile as "undefined".
I am using IndexedDB as my data store.
My home page code looks like this:
WinJS.UI.Pages.define("/pages/home/home.html", {
ready: function (element, options) {
WinJS.Utilities.query("a").listen("click", function (e) {
e.preventDefault();
WinJS.Navigation.navigate(e.currentTarget.href);
}, false);
viewModel = new HomeViewModel(element);
viewModel.load(); //loads from indexed db
},
//etc...
To Do Page:
WinJS.UI.Pages.define("/pages/ToDo/ToDo.html", {
ready: function (element, options) {
viewModel = new ToDoViewModel(element);
viewModel.load();
},
etc//
I know there isn't much to go off, but any ideas would be appreciated.
Also tips on how to debug something like this would be great.
Update
I narrowed it down to this one line from the Hub Page:
myLib.GetData(todaysDate, function (result) {
that.trendsModel.today = result;
WinJS.Binding.processAll(that.el.querySelector("#dataPanel"), that.trendsModel); //<--Right Here
});
If I remove that, then when I load the second page the data doesn't show as undefined. What is interesting is the data initially shows correctly on the second page and then it changes to "undefined".
Solution
My fix:
myLib.GetData(todaysDate, function (result) {
var element = that.el.querySelector("#dataPanel");
that.trendsModel.today = result;
if(element) {
WinJS.Binding.processAll(element, that.trendsModel);
}
});
At the point when when the callback returns, I am already on the second page. So the selector was not found returning null. If you pass null to processAll it tries to bind the whole page which is why I was able to see the correct data for a second then it changes to undefined...Wow, what a doozy. I guess it makes sense but what a pain to find.
Hope it helps someone in the future :)
Your ToDoViewModel, and HomeViewModel need to be observable. This means they need to mix in from WinJS.Binding.mixin, and for the properties that you pull in asynchronously, they need to call this.notify("propertyName", newVal, oldVal) from the property setter.
Note that you need to have getter/setter properties. e.g.
var bindingBase = WinJS.Class.mix(function() {}, WinJS.Binding.mixin);
WinJS.Namespace.define("YourNamespace", {
ToDoViewModel: WinJS.Class.derive(bindingBase, function constructor() {
}, {
_titleStorage: "",
title: {
get: function() { return this._titleStorage; },
set: function(newValue) {
if(newValue === this._titleStorage) {
return;
}
var old = this._titleStorage;
this._titleStorage = newValue;
this.notify("title", newValue, old);
}
}
}),
});
myLib.GetData(todaysDate, function (result) {
var element = that.el.querySelector("#dataPanel");
that.trendsModel.today = result;
if(element) {
WinJS.Binding.processAll(element, that.trendsModel);
}
});
At the point when when the callback returns, I am already on the second page. So the selector was not found returning null. If you pass null to processAll it tries to bind the whole page which is why I was able to see the correct data for a second then it change to undefined...Wow, what doozy. I guess it makes sense but what a pain to find.
In one of my projects I use a dijit.Tree control. I need to add a search to the tree and show only those nodes/leafs which have the searched term in them. However I can't seem to figure out how that can be achieved. Could anybody please help me?
im not entirely sure that your question entirely but it should give hint whereas to go.
Lets use reference documentation example as offset, there is 1) a store 2) a model and 3) the tree
var store = new ItemFileReadStore({
url: "{{dataUrl}}/dijit/tests/_data/countries.json"
});
var treeModel = new ForestStoreModel({
store: store,
query: {"type": "continent"}, // note, this bit
rootId: "root",
rootLabel: "Continents",
childrenAttrs: ["children"]
});
new Tree({
model: treeModel
}, "treeOne");
Interpret the above as such; You have loaded all known countries and continents but 'user' has selected only to show continents by using query on the model - and the hierachy is then represented in a tree structure.
You want a textbox with searching capeabilities, so we hook into onChange
new dijit.form.TextBox({
onChange: function() {
...
}
});
First bit, getting variables
var searchByName = this.get("value");
var oQuery = treeModel.query;
Next, set a new query on the model - preserving the old ones with an object mixin
treeModel.query = dojo.mixin(oQuery, { name: '*'+searchByName+'*' });
Last, notify the model and its tree that changes has occurred - and requery the visible items.
treeModel._requeryTop();
NB If the top-level item (for ForestModel) is not visible, none of its child elements will show, even if the search-string matches those. (Examplewise, Alabama is not shown if US Continent is not matched by query)
EDIT
As OP has the agenda to go by the 'NB', this may not fit needs 100% but its what dojo offers with dijit.Tree.. As it will get rather a lengthy process to recode the model/store queries to include parentbranches up until root i will not do this here - but there are a few tricks still ;)
var tree = new dijit.Tree( {
/**
* Since TreeNode has a getParent() method, this abstraction could be useful
* It sets the dijit.reqistry id into the item-data, so one l8r can get parent items
* which otherwise only can be found by iterating everything in store, looking for item in the parent.children
*
*/
onLoad : function() {
this.forAllNodes(function(node) {
// TreeNode.item <-- > store.items hookup
node.item._NID = node.domNode.id
});
},
/* recursive iteration over TreeNode's
* Carefull, not to make (too many) recursive calls in the callback function..
* fun_ptr : function(TreeNode) { ... }
*/
forAllNodes : function(parentTreeNode, fun_ptr) {
parentTreeNode.getChildren().forEach(function(n) {
fun_ptr(n);
if(n.item.children) {
n.tree.forAllNodes(fun_ptr);
}
})
}
});
(non-tested, but might just work) Example:
// var 'tree' is your tree, extended with
tree.forAllNodes = function(parentTreeNode, fun_ptr) {
parentTreeNode.getChildren().forEach(function(n) {
fun_ptr(n);
if(n.item.children) {
n.tree.forAllNodes(fun_ptr);
}
})
};
// before anything, but the 'match-all' query, run this once
tree.forAllNodes(tree.rootNode, function(node) {
// TreeNode.item <-- > store.items hookup
node.item._NID = node.domNode.id
});
// hopefully, this in end contains top-level items
var branchesToShow = []
// run fetch every search (TextBox.onChange) with value in query
tree.model.store.fetch(query:{name:'Abc*'}, onComplete(function(items) {
var TreeNode = null;
dojo.forEach(items, function(item) {
TreeNode = dijit.byId(item._NID+'');
while(TreeNode.getParent()
&& typeof TreeNode.getParent().item._RI == 'undefined') {
TreeNode = TreeNode.getParent();
}
branchesToShow.push(TreeNode.item);
});
}});
// Now... If a success, try updating the model via following
tree.model.onChildrenChange(tree.model.root, branchesToShow);