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.
Related
I have a GridPanel component in my EXT.NET MVC project, and I would like to create a dynamic tooltip that will display the text/data in each cell when hovered over. Since the .ToolTips() component isn't compatible with this, I am using JavaScript to try to render a dynamic tooltip. My current code creates HTML elements, and then adds tooltips to them:
var el = Ext.getBody().createChild({
html: '<div data-num="1" class="item">Foo</div>' +
'<div data-num="2" class="item">Bar</div>' +
'<div data-num="3" class="item">Baz</div>' +
'<div class="notip">No tip here</div>'
});
new Ext.tip.ToolTip({
target: el,
delegate: '.item',
listeners: {
beforeshow: function (tip) {
var current = tip.currentTarget.dom;
tip.setHtml('Item #' + current.getAttribute('data-num'));
}
}
});
And here is the code for the GridPanel I want to attach it to:
Html.X().GridPanel()
.Title("Request Priorities")
.ID("reqPrioritiesGrid")
.ColumnWidth(1)
.MarginSpec("0 0 0 0")
.Flex(1)
.ToolTips(t => t.Add(Html.X().ToolTip().Html("hello").ID("storeTip").Target("App.storeReqPriorities")))
.Border(true)
.Store(
Html.X().Store()
.ID("storeReqPriorities")
.AutoLoad(true)
.DataSource(Model.RequestPriorities)
.Model(
Html.X().Model()
.Fields(f =>
{
f.Add(Html.X().ModelField().Name("RequestPriorityKey").Type(ModelFieldType.Int));
f.Add(Html.X().ModelField().Name("RequestPriorityName").Type(ModelFieldType.String));
f.Add(Html.X().ModelField().Name("RequestPriorityDescription").Type(ModelFieldType.String));
f.Add(Html.X().ModelField().Name("SortOrder").Type(ModelFieldType.Int));
f.Add(Html.X().ModelField().Name("ResponseTarget").Type(ModelFieldType.String));
f.Add(Html.X().ModelField().Name("ResponseFormat").Type(ModelFieldType.String));
f.Add(Html.X().ModelField().Name("ResponseSLA").Type(ModelFieldType.String));
})
)
.ServerProxy(
Html.X().AjaxProxy()
.Url(Url.Action("ManageLists_GetRequestPriorities", "Admin", new { area = "Cadence" }))
)
)
.Listeners(l =>
{
l.Select.Handler = "handleReqPopulate(record.data);" + "toggleEditRequest();" + "resetAddNew();";
})
.ColumnModel(
Html.X().Column().Flex(1).Text("Request Priority Name").DataIndex("RequestPriorityName"),
Html.X().Column().Flex(3).Text("Request Priority Desciption").DataIndex("RequestPriorityDescription"),
Html.X().Column().Flex(1).Text("Sort Order").DataIndex("SortOrder"),
Html.X().Column().Flex(1).Text("Response Target").DataIndex("ResponseTarget"),
Html.X().Column().Flex(1).Text("Response Format").DataIndex("ResponseFormat"),
Html.X().Column().Flex(1).Text("Response SLA").DataIndex("ResponseSLA")
)
Is there a method similar to .createChild() used in the JavaScript above that can attach a tooltip to an element that is being dynamically created in MVC?
You can bind the ToolTip component to the grid's view with a custom show handler that would fetch the cell data (or row, or entire grid) and show the way you instruct. Wouldn't that suffice for your scenario?
In this case you won't be creating child tooltips though, but rather using the same tooltip to show specific data depending on where you hover the mouse over.
Add this after the grid -- yes, outside it. Given your code snippets, the ToolTip component declaration should look like this:
#(Html.X().ToolTip()
.Target("={App.reqPrioritiesGrid.getView().el}")
.Delegate(".x-grid-cell")
.TrackMouse(true)
.Listeners(l => l.Show.Handler="onShow(this, App.reqPrioritiesGrid)")
)
Then have a handler fill the tooltip's contents like this:
var onShow = function (toolTip, grid) {
var view = grid.getView(),
store = grid.getStore(),
record = view.getRecord(view.findItemByChild(toolTip.triggerElement)),
column = view.getHeaderByCell(toolTip.triggerElement),
data = record.get(column.dataIndex);
toolTip.update(data);
};
From this point, you could further customize the show function to build the tooltip the way you need it.
A grid with a per-cell tooltip is showcased in Ext.NET examples (WebForms) at Miscellaneous > Tooltips > GridPanel Cell Tooltip.
Hope this helps!
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();
}
}
});
});
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 have a problem with the pull down refresh. It works the first time, but then if I change to a different view, then come back to the original view, the Pull to refresh and Release to refresh text seem to get duplicated and overlapped on itself. I am "hardcoding" the datasource's data here, I don't want to use the transport ajax.
I am trying to manually update the data in the setOptions pull method, instead of letting Kendo update it via ajax. The actual data update works. There are no Javascript errors and I get the same result in Chrome and Firefox.
First time works:
After moving to another view, then back to this view, then pulling down:
My view code is:
<div id="subitem-view" data-role="view" data-show="showSubItems">
<div data-role="header">
<div data-role="navbar">
</div>
</div>
<ul id="subItemList" class="itemList">
</ul>
<script id="subItemTemplate" type="text/x-kendo-template">
#:Name#
</script>
</div>
Javascript:
function showSubItems(e) {
var subItems = new kendo.data.DataSource({
data: [
{ Name : "Test1" },
{ Name : "Test2" }
]
});
e.view.element.find("#subItemList").kendoMobileListView({
dataSource: subItems,
pullToRefresh: true,
template: kendo.template($("#subItemTemplate").html())
});
if (typeof (e.view.scroller.pull) == "undefined") {
e.view.scroller.setOptions({
pull: function () {
console.log("pull event...");
subItems.data([
{ Name : "Test1 Updated" },
{ Name : "Test2 Updated" }
]);
setTimeout(function () { e.view.scroller.pullHandled(); }, 400);
}
});
}
}
You're initializing your Kendo UI Mobile ListView on every View show which leads to unpredictable results, like recreating the pull to refresh labels. You should do it in the Init event only.
I'm adding a custom context menu item to documents (and not folders) in a SPDocumentLibrary list in SharePoint. The following code works, but always adds my custom menu item at the top of the context menu, which I don't want. I was guessing that the final parameter in CAMOpt was a sequence number that would define the order, but it doesn't seem to have any effect. Does anyone know if it's possible to add a custom context menu item to the bottom on the list?
function Custom_AddDocLibMenuItems(m, ctx) {
var otype = currentItemFSObjType = GetAttributeFromItemTable(itemTable, "OType", "FSObjType");
if (otype != 1) {
var itemId = GetAttributeFromItemTable(itemTable, "ItemId", "Id");
var listId = ctx.listName;
var action = 'Go_To_Page("' + ctx.HttpRoot + '/_layouts/custom/PAGES/mycustompage.aspx?ListId=' + listId + '&ListItemID=' + itemId + ');';
CAMOpt(m, 'Custom Menu Item', action, '/_layouts/custom/IMAGES/action.gif', '', 110);
CAMSep(m);
}
return false;
}
function Go_To_Page(page) {
window.location = page;
}
Is there any reason why you aren't able to use a custom action feature? For example use feature element code similar to the following but amend the Sequence number of the CustomAction element to locate the menu option:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Id="DoSomething"
RegistrationType="ContentType"
RegistrationId="0x0101"
Location="EditControlBlock"
Sequence="10001"
ImageUrl="/_layouts/images/action.gif"
Title="Do Something">
<UrlAction Url="~site/_layouts/custom/PAGES/mycustompage.aspx?ListItemID={ItemId}&ListId={ListId}" />
</CustomAction>
</Elements>
Setting the content type should ensure the feature doesn't apply to folders. If for some reason it is showing and there appears to be no way to disable it, you could use JavaScript/jQuery to hide the menu option from folders.
In the elements XML for your feature where you include the User Control, add the sequence there:
Control Id="AdditionalPageHead"
ControlSrc="~/_ControlTemplates/blah/blah.ascx" Sequence="229"