ExpandableSearchComponent.html:
<div class="${baseClass}">
<div data-dojo-type="dijit/_HasDropDown" data-dojo-props="dropDown: 'containerNode'">
<div data-dojo-type="dijit/form/TextBox"
name="${SearchViewFieldName}Textbox"
class="${baseClass}Textbox"
data-dojo-props="placeholder:'${fieldName}'"></div>
<div class="${baseClass}Container" data-dojo-attach-point="containerNode"></div>
</div>
</div>
ExpandableSearchComponent.js:
/**
* Javascript for ExpandableSearchComponent
*/
define([ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin",
"dojo/text!./templates/ExpandableSearchComponent.html",
"dijit/form/TextBox", "dijit/_HasDropDown" ], function(declare,
_WidgetBase, _TemplatedMixin, template, TextBox) {
return declare([ _WidgetBase, _TemplatedMixin ], {
templateString : template,
SearchViewFieldName : "",
fieldName : ""
});
});
Intended to be used like this:
<div data-dojo-type="js/widgets/ExpandableSearchComponent"
data-dojo-props="SearchViewFieldName: 'machineSearchView.name', fieldName: 'Name:'">
<div data-dojo-type="dojo/store/Memory"
data-dojo-id="machineNameStore"
data-dojo-props="<s:property value='%{getNameJsonString()}'/>"></div>
<s:set name="MachineName" value="machineSearchView.name"
scope="request"></s:set>
<div data-dojo-type="dijit/form/ComboBox"
data-dojo-props="store:machineNameStore, searchAttr:'name', value:'${MachineName}'"
name="machineSearchView.name" id="machineSearchView.name"></div>
</div>
The intent is:
The user at first only sees the textbox with the placeholder.
When they click it, the containerNode expands and shows what's inside the containerNode, which can either be a dijit/Select, a dijit/form/ComboBox or a dijit/form/FilteringSelect. The internal element is also automatically expanded.
The user selects a value in the internal select, which then gets modified a bit so it's shown in the TextBox as ${fieldName}:${value}.
The data that's eventually processed by the server is the value of the internal element.
The problem I'm currently facing is that I have no idea how to make the _HasDropDown work properly as mentioned above. It renders the TextBox as an element with height 0 and then immediately shows the internal element. I'm not sure how to bind the internal nodes for it to work like a dropdown should work.
dijit/_HasDropDown is a mixin to add dropdown functionality to a widget by inheritance. It is not intended to be used as a widget, especially in declarative markups.
dijit/_HasDropDown is a dijit widget mixin that provides drop-down
menu functionality. Widgets like dijit/form/Select,
dijit/form/ComboBox, dijit/form/DropDownButton, and
dijit/form/DateTextBox all use dijit/_HasDropDown to implement their
drop-down functionality.
Please refer this document on how to use dijit/_HasDropDown. http://dojotoolkit.org/reference-guide/1.10/dijit/_HasDropDown.html
define([ "dojo/_base/declare", "dijit/form/Button", "dijit/_HasDropDown" ],
function(declare, Button, _HasDropDown){
return declare([Button, _HasDropDown], {
isLoaded: function(){
// Returns whether or not we are loaded - if our dropdown has an href,
// then we want to check that.
var dropDown = this.dropDown;
return (!!dropDown && (!dropDown.href || dropDown.isLoaded));
},
loadDropDown: function(callback){
// Loads our dropdown
var dropDown = this.dropDown;
if(!dropDown){ return; }
if(!this.isLoaded()){
var handler = dropDown.on("load", this, function(){
handler.remove();
callback();
});
dropDown.refresh();
}else{
callback();
}
}
});
});
Related
I apologize, but I'm not able to provide a working jsFiddle snippet. I will update the question if I understand how to put the code below in it.
Using dojox/mobile I populate an EdgeToEdgeStoreList with custom ListItems. Some code:
html (jade)
div(data-dojo-type="dojox/mobile/View")
h1(data-dojo-type="dojox/mobile/Heading") Device List
div(data-dojo-type="dojox/mobile/ScrollablePane")
ul#list(data-dojo-type="dojox/mobile/EdgeToEdgeStoreList" data-dojo-props="itemRenderer: DeviceListItem, select: 'single'")
js
var store;
var list = registry.byId("listDevices");
var devices = JSON.parse("a string received from server");
store = new Memory({data: devices, idProperty: "label"});
list.setStore(store);
DeviceListItem
define([
"dojox/mobile/ListItem",
"dijit/_TemplatedMixin",
"dojo/_base/declare"
], function (ListItem, TemplatedMixin, declare) {
var template =
"<div class='deviceDone${done}'>" +
" ${id} - <div style='display: inline-block;' data-dojo-attach-point='labelNode'></div>" +
" <div class='deviceCategory'>${category}</div>" +
"</div>";
TemplatedListItem = declare("DeviceListItem",
[ListItem, TemplatedMixin], {
id: "",
label: "",
category: "",
done: "false",
templateString: template
}
);
});
It works fine, that is I will see my custom ListItems.
But if I resize the window (on desktop browsers) or change orientation (on mobile ones) only the ${id} field remains visible. The others (label and category) disappear. The behavior is the same in all browsers (that I tried).
After debugging I discovered the following. Before any resize the actual html of a ListItem looks like this:
<div id="item1728" class="deviceDoneFalse mblListItem mblListItemUnchecked" tabindex="0" widgetid="item1728" aria-selected="false" role="option">
item1728 -
<div style="display: inline-block;" data-dojo-attach-point="labelNode">n.a.</div>
<div class="deviceCategory">General purpose</div>
</div>
and it's like the template string. After a resize the inner div becomes:
<div style="display: block;" data-dojo-attach-point="labelNode">n.a.</div>
without "inline" all the layout will mess-up and thus the fields "disappear" (actually go below, behind the next row).
I wonder why this happens - the display style is hardcoded into the template strings!
Furthermore, I inspected the CSS rules at runtime, and it's not due to them, it's the html that has changed - indeed.
ListItem (source in dojox/Mobile/ListItem.js) has the following function:
resize: function(){
if(this.variableHeight){
this.layoutVariableHeight();
}
// labelNode may not exist only when using a template (if not created by an attach point)
if(!this._templated || this.labelNode){
// If labelNode is empty, shrink it so as not to prevent user clicks.
this.labelNode.style.display = this.labelNode.firstChild ? "block" : "inline";
}
},
This function is called after a resize and as you can see sets the labelNode display style to "block".
You can replace this function when you define your DeviceListItem, keeping the original source as is but changing the display style.
So I've been struggling for quire sometime to do this. I am building a dynamic form generator tool. One of the functions is that the user should be able to use a jQuery slider to select the font size. I use Underscore templates to create divs for the slider. So everytime a user selects a Label input, this underscore template is loaded and I call the slider() fn to initialize it. Here's is the part of the code that does this
UnderscoreTemplate
<!-- template for plain text inpt to be shown in form generate -->
<script type="text/template" id="text-generate-template">
<div class="row">
<div class="col-sm-10">
<input type="text" placeholder="Enter text here" class="label-name form-control" value="<%=model.get('label')%>">
</div>
<button type="button" class="close" id="remove-element" >×</button>
</div>
<div class="row col-sm-6" style="margin-top:10px">
<div id="slider-<%=model.cid%>"></div>
<small>Font Size: <%=model.get('fontSize')%>px </small>
</div>
</script>
So as per the code ablove, each dynamically loaded element has a unique slider with a unique ID.
The Backbone Model extends from a Base element called "Element"
var TextElement = Element.extend({
defaults:function(){
return _.extend({}, Element.prototype.defaults,{
name: 'PlainText',
generateTemplateType: 'text-generate-template',
previewTemplateType:'text-template',
textAlign: 'center'
});
}
});
The view which is responsible for generating the html is
var ElementView = Backbone.View.extend({
tagName: 'li',
events : {
},
initialize : function(){
this.listenTo(this.model,'change', this.render);
this.render();
},
render : function(){
var htmlContent = $('#'+this.model.get('generateTemplateType')).html();
var content = _.template( htmlContent, {elementType : elementTypes, model : this.model} );
this.$el.html( content );
me = this;
if(this.model.get('name') == 'PlainText'){
this.$el.find('#slider-'+this.model.cid).slider({
min: 10,
max:40,
step: 1,
value:me.model.get('fontSize') > 9 ? me.model.get('fontSize') : 24,
change: function(event, ui) {
me.slide(event, ui.value);
}
});
}
return this;
},
slide: function(event, index){
console.log("the models id in slide function -> "+this.model.cid);
this.model.set('fontSize', index);
}
});
So what I do here is that every time I detect a change, I update this model's fontSize to the new value passed in through the Slider.
THe collection to which this model belongs to recognises this changes and rerenders itself.
When there is only one such element on the page, then everything works fine. The moment the user adds more than one element and tried to change the font size for each item, then only the font size of the last element is changed. Regardless of whether I slide the first , second or the third element it always sets the change to the last model in the collection and the last model get rerendered with the incorrect font size value.
I tried console logging the id of the model in the slide function and it always seems to return the last models ID regardless of which slider I use.
What am I doing wrong here??
You have an accidental global variable right here:
me = this;
A side effect of this is that every instance of ElementView will end up sharing exactly the same me and that me will be the last ElementView you instantiate. Sound familiar?
The solution is to use a local variable:
var me = this;
or, if you don't need the slider's this inside the callback, use a bound function instead:
value: this.model.get('fontSize') > 9 ? this.model.get('fontSize') : 24,
change: _(function(event, ui) {
this.slide(event, ui.value);
}).bind(this)
You could also use $.proxy or Function.prototype.bind if you prefer those over _.bind.
I've created a templated base widget called "Dialog", which I want to use as a core layout widget for all the other within the package. It's a simple container with couple of attach points. (I've not included HTML as it's quite straightforward)
define("my/Dialog",[
"dojo/_base/declare",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"dojo/ready",
"dojo/parser",
"dojo/text!./Dialog.html"], function(declare, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, ready, parser, template){
return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], {
templateString: template,
widgetsInTemplate: true,
title: 'Dialog',
content: null,
constructor: function(args){
},
postCreate: function(){
this.inherited(arguments);
},
///
/// Getters / Setters
///
_setTitleAttr: function(title){
},
_setContentAttr: function(content){
this._contentPane.innerHTML = content;
}
});
ready(function(){
parser.parse();
});});
Then I want to create another templated dialog that will inherit from this one, and will basically extend it in terms of kind-of-injecting the template into the content of the my/Dialog
define("my/BookmarksDialog",[
"dojo/_base/declare",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"dojo/ready",
"dojo/parser",
"my/Dialog"], function(declare, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, ready, parser, Dialog){
return declare([Dialog, _WidgetsInTemplateMixin], {
//templateString: template,
template: '<div data-dojo-attach-point="bookmarkpoint">something</div>',
widgetsInTemplate: true,
content: template, // This works but doesn't parse the widgets within
title: 'Bookmarks',
constructor: function(args){
},
postCreate: function(){
this.inherited(arguments);
}
});
ready(function(){
parser.parse();
});});
The problem I'm facing is that the attach point called bookmarkpoint is not accessible for the BookmarkDialog
So the question is:
How do I get the BookmarkDialog template get parsed and placed in the Dialog widget?
Options:
Copy the template of the Dialog widget in to BookmarkWidget is
not really an option,
Instantiate the Dialog in the BookmarkWidget template is not much of an option either, as I don't want the Dialog to be under an attach point. I'd have to wrap all
the methods of Dialog into BookmarkDialog to propagate correctly.
Please note that I'm also firing the .startup() upon instantiating the widget.
Thanks
Use buildRendering method, which is designed for this purpose. In this method you can parse templateString property, modify it and then reassign it.
buildRendering: function () {
// Parse template string into xml document using "dojox/xml/parser"
var xml = xmlParser.parse(this.templateString);
var xmlDoc = xml.documentElement;
// Find target node which should be modified
var targetNode = query("div[data-dojo-attach-point='targetNode']", xmlDoc)[0];
// Create new template content using createElement, createAttribute,
// setAttributeNode
var newNode = xml.createElement("button");
// Append content to the xml template
targetNode.appendChild(newNode);
// Re-set the modified template string
this.templateString = xmlParser.innerXML(xmlDoc);
this.inherited(arguments);
}
Thanks for that, as I see I was trying to do the magic in wrong place "postMixInProperties"! This has actually led me to very similar solution and that is to simply replace the base widget's template string's variable with the template string of the enhancing widget.
Consider this base widget's template string. Please notice the {0}.
<div class="dialog" data-dojo-attach-event="onkeyup:_keyUp" >
<div data-dojo-attach-point="_wrapper" tabindex="1" role="button">
<div data-dojo-attach-point="pendingPane"></div>
<div class="inner commonBackground unselectable">
<span class="hideButton" data-dojo-attach-point="closebtn" data-dojo-attach-event="onclick:close" aria-hidden="true" data-icon=""></span>
<div class="header" style="cursor: default;" data-dojo-attach-point="_titlePane"></div>
<div data-dojo-attach-point="_contentPane" class="contentPane">{0}</div>
</div>
</div>
Then the template string of the enhanced widget
<div class="bookmarks">
<div data-dojo-attach-point="bookmarkpoint">
test attach point
</div>
</div>
And then in the enhanced widget's "buildRendering" I've used the following
return declare([Dialog], {
title: 'Bookmarks',
constructor: function(args){
},
buildRendering: function(){
this.templateString = lang.replace(this.templateString, [template]);
this.inherited(arguments);
},
postCreate: function(){
this.inherited(arguments);
this._cookiesCheck();
}, ...
where "template" variable comes from one of the requires
"dojo/text!./BookmarksDialog.html",
Maybe there is a simpler and kind of out-of-the-box way in dojo how to achieve the above mentioned, but this did the trick for me.
Hope that helps!
I'm using Backbone and JQuery and would like to create a Backbone View to create equal height columns as described in method 2 of this link. The HTML looks like this:
<div class="container">
<div id="leftcolumn" class="set_equal_height"> … Lots Of Content … </div>
<div id="middlecolumn" class="set_equal_height"> … Lots Of Content … </div>
<div id="rightcolumn" class="set_equal_height"> … Lots Of Content … </div>
</div>
I put together the following Backbone View but it's not working presumably because the loop using .each is not working (the page loads with no errors, but the height of the columns is not modified by the javascript):
define([
'jquery',
'underscore',
'backbone',
], function($, _, Backbone) {
var SetEqualHeight = Backbone.View.extend({
initialize: function() {
var tallestcolumn = 0;
console.log(this.$el.height());
console.log(this.$el.attr("id"));
this.$el.each(function() {
currentHeight = $(this).height();
console.log(currentHeight);
if(currentHeight > tallestcolumn) {
tallestcolumn = currentHeight;
}
});
this.$el.height(tallestcolumn);
},
});
return SetEqualHeight;
});
I'm defining the "el" argument in a separate Wire specification as follows: { el: 'div.set_equal_height' }, and it is passing correctly since the "console.log(this.$el.height())" and console.log(this.$el.attr("id")) in the code above print out correctly. However the console.log inside the ".each" statement doesn't print out, showing that there is a problem with the ".each". I looked at this question and tried out the Underscore "_.each" method but could not figure out how to iterate through elements with a given class (i.e. div.set_equal_height) instead of a given array. Could anyone enlighten me about making .each work in the above Backbone View? Please!!!
Just for reference, the following function works by itself (I'm looking to incorporate it as a Backbone view or view helper):
function setEqualHeight(columns) {
var tallestcolumn = 0;
columns.each(function() {
currentHeight = $(this).height();
if(currentHeight > tallestcolumn) {
tallestcolumn = currentHeight;
}
});
columns.height(tallestcolumn);
}
$(document).ready(function() {
setEqualHeight($("div.set_equal_height"));
});
I assume you are instantiating your view with something like this?
new SetEqualHeight({el: '.container')
If so, that means that $el will be set to the container div inside your view. $(anything).each will iterate though all members of "anything" ... but in your case there is only one member ('.container').
The solution is to change this line:
this.$el.each(function() {
to:
this.$el.find('.set_equal_height').each(function() {
or better yet (more Backbone-y):
this.$('.set_equal_height').each(function() {
Hope that helps.
I'm using Dojo's Dijit Layout for generating content tab-panes similar to Dijit Theme Tester Demo. All of the tabs here are closeable.
The issue is: when I close a tab it goes back to the first tab in the list instead of the previous tab (available next to it).
You can think of it like opening a new tab in Firefox or Chrome and try closing the last tab.... on closing tab, it changes the focus to the previous tab which is a predictable behavior for working with tabs. But with dijit.TabContainers, by default it goes back to the very first tab instead of previous one. This is a serious flaw when you consider the UI basics.
I have checked with dojo docs, but don't found any hint on this. Any idea how to it?
Ok so when the [X] button on the dijit.layout.ContentPane (tab) is clicked an event onClose is generated, the dijit.layout.TabContainer is listening to this event, so when this happens, it executes the callback closeChild() then the function removeChild() is executed, this last one is the one you should override.
The tabContainer inherits this two functions from dijit.layout.StackContainer you should check the API documentation.
So for being able to modify the default behavior of the closing tabs, you should override the default functionality, in the example below i do this. Read the comments for info on where to add your logic.
E.g.
<script>
require([
"dojo/parser",
"dojo/_base/lang", //this is the one that has the extend function
"dojo/topic", //this is needed inside the overrided function
"dijit/layout/TabContainer",
"dijit/layout/ContentPane",
"dojo/domReady!"
], function(Parser, lang, topic, tabContainer, contentPane){
Parser.parse();
// this will extend the tabContainer class and we will override the method in question
lang.extend(tabContainer, {
// this function, i copied it from the dijit.layout.StackContainer class
removeChild: function(/*dijit._Widget*/ page){
// for this to work i had to add as first argument the string "startup"
this.inherited("startup", arguments);
if(this._started){
// also had to call the dojo.topic class in the require statement
topic.publish(this.id + "-removeChild", page);
}
if(this._descendantsBeingDestroyed){ return; }
if(this.selectedChildWidget === page){
this.selectedChildWidget = undefined;
if(this._started){
var children = this.getChildren();
if(children.length){
// this is what you want to add your logic
// the var children (array) contains a list of all the tabs
// the index selects the tab starting from left to right
// left most being the 0 index
this.selectChild(children[0]);
}
}
}
if(this._started){
this.layout();
}
}
});
// now you can use your modified tabContainer WALAAAAAAA!
// from here on, normal programmatic tab creation
var tc = new tabContainer({
style: "height: 100%; width: 100%;",
}, "tab-container-div-id");
var cp1 = new contentPane({
title: "First Tab",
closable: true
});
tc.addChild(cp1);
var cp2 = new contentPane({
title: "Second Tab",
closable: true
});
tc.addChild(cp2);
var cp3 = new contentPane({
title: "Third Tab",
selected: true,
closable: true
});
tc.addChild(cp3);
tc.startup();
});
</script>
In Dojo 1.10, reverting to the previous tab is the normal behaviour for TabContainers (instead of reverting to the first tab).
Presumably, you could use dojo/aspect to get the old behaviour (warning: not tested):
require( [ 'dijit/registry', 'dojo/aspect', 'dojo/_base/lang' ],
function( registry, aspect, lang )
{
var tabContainer = registry.byId( 'tab_container');
aspect.before( tabContainer, 'removeChild', lang.hitch( tabContainer, function( page )
{
if(this.selectedChildWidget === page)
{
this.selectedChildWidget = undefined;
if(this._started)
{
var children = this.getChildren();
this.selectChild( children[0] );
}
}
return page;
} ) );
}
);
Or, alternatively, you could use the onClose extension point on a tab's ContentPane:
require( [ 'dijit/registry', 'dojo/_base/lang' ],
function( registry, lang ) {
newTabPane.onClose = lang.hitch(this, function () {
// select first
var tabContainer = registry.byId('tab_container'),
all_tabs = tabContainer.getChildren();
tabContainer.selectChild( all_tabs[0] );
// allow save to go ahead
return true;
});
}
);
Obviously, both these approaches would allow you to select a specific different pane on a tab being closed instead with a little tweaking...