With knockout you can produce a row of bootstrap nav-tabs like this
<ul class="nav nav-tabs question-container" data-bind="foreach: Questions">
<li role="presentation" data-bind="css: { active: $index() === 0 }">
<a data-toggle="tab"
data-bind="attr: { href: '#q' + QuestionId() }">, text: Caption"></a>
</li>
</ul>
With bootstrap-sortable you can make the tabs sortable simply by changing the binding. A limitation clearly documented is that you can't use the sortable binding with virtual elements because it depends on being defined on a single container element.
Suppose I wanted to add a tab "Add a new question" presenting various options for what to create, after the manner of the add-new tabs used in Chrome, Internet Explorer, Firefox and Microsoft Excel.
If I weren't trying to make the tabs sortable, I'd do this:
<ul class="nav nav-tabs question-container">
<!-- ko foreach: Questions-->
<li role="presentation" data-bind="css: { active: $index() === 0 }">
<a data-toggle="tab"
data-bind="attr: { href: '#q' + QuestionId() }, text: Caption"></a>
</li>
<!-- /ko -->
<li role="presentation" data-bind="css: { active: Questions().length === 0 }">
<a data-toggle="tab" href="#addQuestion">
<i class="fa fa-plus"></i>Add a new question</a>
</li>
</ul>
By adding a marker class "sortable-item" to the <LI> in the template, we can trivially exclude the meta item by defining item: ".sortable-item" in the bootstrap-sortable options. But it still won't fly, because you can't use virtual elements with bootstrap-sortable.
Does anyone know how to add meta items to a collection managed by bootstrap-sortable?
There are four ways this problem can be approached.
Don't use bootstrap-sortable, apply jQuery-UI sortable explicitly and ise sort update events to update the backing store collection.
Use bootstrap and inject the meta items after binding completes.
Modify bootstrap to support the notion of a meta-items template in addition to the binding template.
Modify bootstrap to use the immediate parent of a virtual node. There is always a parent, even if it's BODY.
Personally I favour option four, because it doesn't change how bootstrap-sortable is applied, it simply removes a limitation. No breaking changes.
Option three is nearly as good but it increases the conceptual complexity and makes the learning curve steeper.
Option two sucks big rocks. It screws up separation of concerns.
Option one, which is what I actually did, sucks even bigger rocks. Not only does it pollute the view model with UI behaviour, it's complicated to do, depends on poorly documented and obscure knockout internal utilities and it's just plain ugly. Everything about it is a justification for the existence of bootstrap-sortable.
In answer to this comment:
The "add question" button is not a question, therefore it should not
be part of the list of questions
It's not a list of questions, it's the UI for a set of operations that can be performed on a list of questions. Most of them are "display item X for editing" but one of them is "create a new item". Also present are controls for deleting items and for re-ordering them.
I find it hilariously ironic that anyone would claim this is not an obvious UI design while using a web browser - a ubiquitous example of this exact design
It doesn't seem like there is currently a better story than the workaround I mention in the question. The ideal solution would be for me or someone else to write a knockout binding. Most of the code in bootstrap-sortable seems to have to do with templating the rather nifty table it generates, but I think the required lessons are there.
Related
I am using Dotclear blog software, which provide a widget for the blog categories with a resulting code looking like this:
<div class="widget categories ">
<h3>Catégories</h3>
<ul>
<li>Cat1</li>
<li>Cat2
<ul>
<li>Subcat1</li>
</ul>
</li>
<li>Cat3
<ul>
<li>Subcat2</li>
</ul>
</li>
<li>Cat4</li>
</ul>
</div>
What I am trying to achieve here is using the <a> tag (for Cat2 or Cat3) as header (or a dynamically added <h4> around it) and fold the subcategory list. If I call the accordion like this :
$(".categories").accordion({
header: "li:has(ul) > a",
});
the accordion does work, but when I click on the link it just folds/unfolds the item and doesn’t let me go to the link target (the category page, that is).
I tried wrapping the <a> in an <h4> tag and use that tag as header, but it doesn’t seem to make a difference. Is there a way to do what I seek or should I abandon the idea of collapsing subcategories and have functioning links within the header ?
Thanks for your time.
Well, after reading your comments, I realized I was using the wrong tool to achieve my objective. I have replaced jquery-ui’s accordion with a treemenu jQuery plugin which is actually made for my use. Sorry to have wasted your time and thanks for your kind answers.
I'm using the jquery Mobile AutoDivider for a web project of mine and it works great in IE8, but for some reason in Chrome it's not generating the headers for me.
My question is: How exactly does the AutoDivider determine what to make a 'divider'? Is is just the first item within your <li></li>?
Here's my basic HTML structure (it's ultimately placed in a ASP.Net Repeater:
<ul data-role="listview" data-autodividers="true">
<li>
<img src="mySource.jpg" alt="" />
<h3>John Doe</h3>
<p><strong>Company Name Here</strong></p>
<p>User Address</p>
<p class="ui-li-aside">
<strong style="display: none;"><!-- This is what seems to make the headers in IE, placing this right here: -->
Last Name of Employee</strong>
</p>
</li>
</ul>
see the docu http://jquerymobile.com/demos/1.2.0/docs/lists/docs-lists.html
Autodividers
A listview can be configured to automatically generate dividers for its items. This is
done by adding a data-autodividers="true" attribute to any listview.
By default, the text used to create dividers is the uppercased first letter of the
item's text. Alternatively you can specify divider text by setting the > autodividersSelector option on the listview programmatically.
I have read a few articles as I was searching for a solution. They all seemed to favor a hard-coded or HTML Helper alternative; however, I wanted something simple and database driven. This is my best solution (submitted as an answer, by me).
Here are some other articles' solutions:
An easy way to set the active tab using controllers and a usercontrol in ASP.NET MVC?
asp.net mvc and css: Having menu tab stay highlighted on selection
ASP.NET MVC: Tabs ASCX - Adjust CSS class based on current page?
Just pass a TempData down from one of your controllers like this:
TempData("CurrentPage") = "Nutrition"
Then, in your View add a conditional like this:
<ul>
#For Each item In Model
Dim currentItem = item
If (Not String.IsNullOrEmpty(TempData("CurrentPage")) And TempData("CurrentPage") = currentItem.NAV_Element) Then
#<li><a class="current" href="#Html.DisplayFor(Function(modelItem) currentItem.NAV_Destination)">#Html.DisplayFor(Function(modelItem) currentItem.NAV_Element)</a></li>
Else
#<li>#Html.DisplayFor(Function(modelItem) currentItem.NAV_Element)</li>
End If
Next
</ul>
I have accomplished this in the past by using the route values to generate a href and then setting the parent tab to active based on that.
This is a bootstrap flavoured version:
<ul class="nav nav-tabs">
<li>Some page</li>
<li>Some other Page</li>
</ul>
<script type="text/javascript">
$(function(){
$('a[href="#Url.Action(ViewContext.RouteData.Values)"]').parents("li").addClass("active");
};
</script>
Si
I know I am too late to answer this but if you are using ASP MVC and looking for highlighting Menu items using controller and action, here is the solution which is working perfectly fine for me -
<li> <i class="fa fa-user-plus"></i> <span>Signup</span> </li>
I've checked out a number of samples, but none are quite the same as what I'm trying to do.
What I've got works, mostly, but it doesn't quite work right.
Here's a fiddle to illustrate the issue.
http://jsfiddle.net/5yA6G/4/
Notice that the top set works fine, but it's statically defined.
The bottom set (Tom, steve, bob) "work" basically, but the header element ends up both in the collapsible header AND in the portion of the collapsible that gets hidden.
Seems like I must be doing something wrong, but I haven't been able to figure out what.
Any ideas?
Just for reference and for anyone else running into this problem, it turns out to be at least somewhat obvious in hindsight.
Knockout's built in "anonymous" templating works great in many cases, but with JQMobile, it can be a tad quirky.
That's because JQMobile will adjust the content of the anonymous template when the page loads, just as it does with all the other content.
Then, later, when you use knockout's ApplyBindings function, knockout will add the applicable elements, just as it should. As many posts and answers have hinted at, you then MUST call collapsible() on the newly created elements, via something like this.
$("div[data-role='collapsible']").collapsible({refresh: true});
No problem there. HOWEVER, if JQM has already applied formatting, then the anonymous template has already been "rendered" by JQM, so rendering it again by calling collapsible causing all sorts of funky results, including doubled heading, nested collapsibles, etc.
The solution for me was to use Knockout's "Named Template" feature, and just put the template to render the collapsible elements into a tag, like this:
<script type="text/html" id="alarm-template">
<div data-role="collapsible" data-collapsed="true" data-collapsed-icon="arrow-d" data-expanded-icon="arrow-u" data-enhance="false">
<h3 data-bind="text:name"></h3>
<p>The content here</p>
<p data-bind="text: name"></p>
</div>
</script>
Doing this prevents JQM from "rendering" the template elements when the page loads, so they'll be rendered properly when they're actually generated.
EDIT: The above works fine for collapsibles NOT in a collapsible-set, but, if they're in a set, I've found the styling of the elements (particularly, the corner rounding to indicate belonging to a set) doesn't work right.
From what I can tell, there are 2 problems:
The first is that just triggering "Create" doesn't actually refresh the styling of all the collapsibles in the set. to do that you have to do...
$("div[data-role='collapsible-set']").collapsibleset('refresh');
But, there's a worse problem. JQM "marks" the last item in the set as the "last item". That fact then gets used to determine how to style the last item as it's being expanded/collapsed.
Since Knockout doesn't actually rebuild the entire set (for speed), each time you call the refresh method, JQM dutifully marks the last item as "last", but never removes the marks on previous items. As a result, if you start from an empty list, EVERY item ends up being marked "last" and the styling fails because of this.
I've detailed the fix for that at github in an issue report.
https://github.com/jquery/jquery-mobile/issues/4645
I actually found a much easier way to do this:
Set up your foreach binding as you normally would for me it looked like this
<div data-bind="foreach: promotions">
<h3 data-bind="text: Title"></h3>
<p>Creator:<span data-bind="text: Creator"></span></p>
<p>Effective Date:<span data-bind="text: EffectiveDate"></span></p>
<span data-bind="text: Description"></span>
<a data-bind="text: ButtonText, attr: {href: ButtonLink}"></a>
Wrap that in a div with class="collapsible like so
<div data-role="collapsible-set" data-bind="foreach: promotions">
<div class="collapsible">
<h3 data-bind="text: Title"></h3>
<p>Creator:<span data-bind="text: Creator"></span></p>
<p>Effective Date:<span data-bind="text: EffectiveDate"></span></p>
<span data-bind="text: Description"></span>
<a data-bind="text: ButtonText, attr: {href: ButtonLink}"></a>
Apply the collapsible widget via jquery mobile after you do your binding like so:
$(document).ready(function () {
ko.mapping.fromJS(data, dataMappingOptions, PromotionViewModel.promotions);
ko.applyBindings(PromotionViewModel);
$('.collapsible').collapsible();
});
For a collapsible set the same idea can be applied just set the class="collapsible-set" on your foreach div. Hope this helps
Here is my problem. I have a few hard coded pseudo pages in my index. Some filled with content, some empty which will be filled on user interaction only by ajax. This ajax content contains html lists. When they load they don't have the nice jquery mobile look so I have to call a .listview() method so the jqm framework parse it on my ajax callback. That is where I often get this JS error:
Uncaught TypeError: Cannot read property 'jQuery162027575719612650573' of undefined
The number is never the same...
I wonder if I use the proper way to parse a listview after the page loads the ajax content. the error seems to be triggered when there is slight lag for the loading and the complete event is triggered too soon and my listview is not yet in the DOM at that moment, just a guess. what is the recommended way to initialize a listview after an ajax call?
It is very unfortunate because when the js error occurs it seems to freeze any further js execution...
so here is my empty pseudo page:
<div data-role="page" id="playlist" data-add-back-btn="true">
<div data-role="header">
<h1><g:message code="pd.playlist" /></h1>
</div>
<div data-role="content"></div>
</div>
right under it there is a script tag with the bind an ajax call on pageshow to activate the listview
<script type="text/javascript">
$('#playlist').bind('pageshow', function () {
$.ajax({
url: "updatePlaylistTemplate.gsp",
error:function(x,e){handleAjaxError(x,e);},
beforeSend:function(){$.mobile.showPageLoadingMsg();},
complete:function(){
$.mobile.hidePageLoadingMsg();
$('[data-role="listview"]').listview(); //re-active all listview
},
success:function(data, textStatus, jqXHR){
$('#playlist').find('[data-role="content"]').html(data);
}
});
});
</script>
The updatePlaylistTemplate return this (extract):
<ul data-role="listview" data-split-theme="d">
<li data-icon="delete">
Provider: Bell
</li>
<li data-icon="delete">
Rock - Classic Rock
</li>
<li data-icon="refresh" data-theme="e">Refresh list</li>
<li data-role="list-divider">Next song</li>
<li>
<a href="urlToViewSongInfo">
<img src="images/song.gif" />
<h3>Albert Flasher</h3>
<p>The Guess Who</p>
<p class="ui-li-aside">Next</p>
</a>
</li>
<li data-role="list-divider">Now playing</li>
<li>
<a href="urlToviewSongInfo">
<img src="images/song.gif" />
<h3>Crime of the Century</h3>
<p>Supertramp</p>
<p class="ui-li-aside">14h49</p>
</a>
</li>
<li data-role="list-divider">Previous songs</li>
<li>
<a href="urlToViewSongInfo">
<img src="images/song.gif"" />
<h3>Desperado</h3>
<p>Alice Cooper</p>
<p class="ui-li-aside">14h45</p>
</a>
</li>
[...]
</ul>
What version of jQuery Mobile are you using? In the latest beta (1.0b2) you can trigger the create event on a dom element to have the framework initialize it:
New “create” event: Easily enhance all widgets at once
While the page plugin no longer calls each plugin specifically, it
does dispatch a “pagecreate” event, which most widgets use to
auto-initialize themselves. As long as a widget plugin script is
referenced, it will automatically enhance any instances of the widgets
it finds on the page, just like before. For example, if the selectmenu
plugin is loaded, it will enhance any selects it finds within a newly
created page.
This structure now allows us to add a new create event that can be
triggered on any element, saving you the task of manually initializing
each plugin contained in that element. Until now, if a developer
loaded in content via Ajax or dynamically generated markup, they
needed to manually initialize all contained plugins (listview button,
select, etc.) to enhance the widgets in the markup.
Now, our handy create event will initialize all the necessary plugins
within that markup, just like how the page creation enhancement
process works. If you were to use Ajax to load in a block of HTML
markup (say a login form), you can trigger create to automatically
transform all the widgets it contains (inputs and buttons in this
case) into the enhanced versions. The code for this scenario would be:
$( ...new markup that contains widgets... ).appendTo( ".ui-page"
).trigger( "create" );
Create vs. refresh: An important distinction
Note that there is an important difference between the create event
and refresh method that some widgets have. The create event is suited
for enhancing raw markup that contains one or more widgets. The
refresh method that some widgets have should be used on existing
(already enhanced) widgets that have been manipulated programmatically
and need the UI be updated to match.
For example, if you had a page where you dynamically appended a new
unordered list with data-role=listview attribute after page creation,
triggering create on a parent element of that list would transform it
into a listview styled widget. If more list items were then
programmatically added, calling the listview’s refresh method would
update just those new list items to the enhanced state and leave the
existing list items untouched.
Link: http://jquerymobile.com/blog/
You can also copy the output that jQuery Mobile creates and use that structure rather than using <li> tags and depending on jQM to inititialize it:
<li data-role="list-divider" class="ui-li ui-li-divider ui-btn ui-bar-a ui-btn-up-undefined" role="heading"><span>List Divider</span></li>
<li data-theme="b" class="ui-li ui-li-static ui-body-b">Regular LI</li>