I'm making a toy app, trying to learn about these libraries and was hoping to understand how I would present a "tab" interface. I'd like to have a few buttons (or links, whatever is most common) on top. When one selected, it looks selected and the main content on the page changes to present what's on that tab.
My learning app is very simple at the moment:
<body>
<div class="content"></div>
</body>
And a backbone view:
var ContentView = Backbone.View.extend({
el: $('.content'),
// ..
So far, I've looked into using <% tag in the html to make a partial, making the ContentView somehow responsible for rendering different stuff in there. The other idea I've had but don't quite know how to pursue is several Backbone views taking turns being in charge of changing the one div.
Would sure appreciate some advice about the canonical approach here, including how to present the tabs buttons and how to cleanly separate view logic for the different tabs.
Follow a 'separation of concerns' model. You've got a main content view, which handles tab navigation. Anytime someone clicks on a tab, that main content view should tell the view that the tab belongs to that it is now active. Then that sub-view handles things from there. Here's some example code:
Let's say this is the HTML:
<div id="tabContainer" class='tabs'>
<div class='tab' id="content1Tab"></div>
<div class='tab' id="content2Tab"></div>
<div class='tab' id="content3Tab"></div>
</div>
<div id="contentContainer">
</div>
This might be the javascript.
ContentView = new (Backbone.View.extend({}
events: {
'click .tab': 'openTab'
},
el: $("#tabContainer"),
tabViews: {},
openTab: function (e) {
var el = $(e.currentTarget);
$("#contentContainer").children().detach();
if(!this.tabViews[el.attr('id')]) {
this.tabViews[el.attr('id')] = this.createTabViewForEl(el);
}
this.tabViews[el.attr('id')].render($("#contentContainer"));
},
createTabViewForEl: function (el) {
var tab;
switch(el.attr('id')) {
case "content1Tab":
tab = new FirstContentTab();
break;
/* etc */
}
return tab;
}
))();
FirstContentTab = Backbone.View.extend({
render: function (targetEl) {
this.setElement($("#someContentEl"));
this.$el.appendTo(targetEl);
}
/** stuff like loading in content for tab, or making a monkey dance when it opens **/
});
There are more elegant ways of doing this, like referencing the module that the tab belongs to, then using requirejs or some other module loader to load in that module and give it the tab in question. But, either way, don't let that one main view do too much. Otherwise you'll end up with something that's way more complicated than it needs to be.
Related
Recently I made use of this SAPUI5 control/attached code. I have implemented the whole thing as recommended in the documentation and in my application now a view (ShellbarSidenav.view.xml) and associated controller (ShellbarSidenav.controller.js) that takes care of navigating through the application. (see code from the linked example).
The situation is that I have created a separate view for each subpage that can be accessed through the navigation. As an example a welcome page (Welcome.view.xml), in which among other things the possibility should be offered to navigate to certain subpages. This means that I want to take the same logic for navigating from the ShellbarSidenav.controller.js and put it into the logic Welcome.controller.js.
This is exactly where the problem sits. I need access to the ShellbarSideNav.view.xml although the press event is triggered by the Welcome.view.xml. Since I am a beginner in SAPUI5, I don't know if this is an architecture related problem and how to solve it.
ShellbarSidenav.controller.js (Functioning, Navigation via Sidenav)
onItemSelect: function (oEvent) {
var item = oEvent.getParameter('item');
this.byId("pageContainer").to(this.getView().createId(item.getKey()));
},
Welcome.controller.js (Not functioning, Trying to navigate via Buttons on Welcome View)
onInit: function () {
var oOtherView = sap.ui.getCore().byId("viewID");
if (!oOtherView) {
oOtherView = sap.ui.xmlview({
id: "viewID",
viewName: "namespace.directory.view.ShellbarSidenav",
});
}
this.oOtherView = oOtherView;
},
onPressTilesNav : function (oEvent) {
var oOtherView = this.oOtherView;
var item = oEvent.getSource().getId().substring(12);
oOtherView.byId("pageContainer").to(oOtherView.createId(item));
}
ShellbarSidenav.view.xml (Origin of the problem, this is the navigation structure which I need to access from the Welcome Controller)
<NavContainer id="pageContainer" initialPage="welcome">
<pages>
<ScrollContainer id="welcome" horizontal="false" vertical="true" height="100%">
<mvc:XMLView viewName="namespace.directory.view.Welcome"/>
</ScrollContainer>
...
</pages>
</NavContainer>
Good Afternoon everyone.
I'm trying to color the comment item when I go to the page with the comment included through notifications. - Like fb or stack overflow.
I have everything working except the part that I mentioned above.
Notification Events
Template.notification.events({
'click a':function() {
var commentTemplate = this.commentId;
console.log(commentTemplate); //Target commentId returns successfully
//Code here needed
//to color comment when page moves to
//coresponding page.
}
Template.commentList
//HTML
{{#each comments}}
{{> commentItem}}
{{/each}}
//JS
comments: function(){
return Comments.find({postId:this._id});
}
I've also tried grabbing the corresponding commentItem's id through console.log using this._id.
So What I would like to know is,
is there a way to link this.commentId from notifications and access <template name = "commentItem"> with corresponding _id. And then manipulate it's element / dom by using things such as .addClass.
Please nudge me in the right direction!
If I understand you correctly, when you click a link in the notifications template, you want to take the user to the commentList page and then manipulate the CSS of the referred-to comment? There are probably a couple of ways to do this.
The first step is going to be to make sure that you have a way to select that particular DOM element once the page loads. For that, in your commentItem template, you might do something like this:
<template name='commentItem'>
<div class='commentItem' id='comment-{{_id}}'>
....
</div>
</template>
If you're using iron:router, a quick and easy way (but not a particularly robust way) to do this would be to manually redirect to the commentList page, then perform your manipulation as part of the event handler once the page has rendered:
Template.notification.events({
'click a':function(event) {
event.preventDefault(); // don't let this take you anywhere
var commentTemplate = this.commentId;
Router.go($(event.currentTarget).attr('href'));
setTimeout(function() {
$('#comment-' + commentTemplate).addClass('whatever-class-you-want');
}, 500); // need to wait for the page to render first -- this is probably not a robust method
}
});
A more robust option, which has the added benefit of persisting on page refresh, might be to add an optional referrer parameter to the URL (i.e., have the link be something like ..., where [comment _id] is replaced by the _id of the comment in question), then in your commentList template, once your page has rendered, you can see if there's a referrer, and if there is, change the CSS:
Template.commentList.rendered = function() {
var referrer = Router.current().params.query.referrer;
if (referrer) {
$('#comment-' + referrer).addClass('whatever-class-you-want');
}
}
We have large SharePoint lists with lots of columns. Our users are forgetting which cells they are viewing because after scrolling the headers disappear (no way to freeze headers like in Excel).
We want to try adding tooltips to the cell items so when they hover over it will display a tooltip with the column name.
Has anyone ever tried doing this before?
I have the following code which works initially on the load but stops working after the user sorts, filters or switches the list into Edit mode:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
<script type="text/javascript">
jQuery(
function()
{
$('td').hover
(
function()
{
var idx = jQuery(this).parent().children().index(jQuery(this));
jQuery(this).attr('title',jQuery(this).parent().parent().parent().find('th').eq(idx).text());
jQuery('div.ms-core-brandingText').html(jQuery(this).parent().parent().parent().find('th').eq(idx).text());
}
)
}
);
</script>
Your code stops working because SharePoint reloads the list content. This is a common issue when adding client side scripts to SharePoint pages.
First, you should actually be able to render a view with frozen headers. Right, it doesn't come out of the box, but there are third party datatable tools available.
Another option is to include your code via the Client Side Rendering option. This is a broad topic, so probably the first step would be to google it.
Okay, getting closer, using CSR instead of just jQuery. This works but needs each field specified manually. Looking for a way to apply this to every field in the view.
<script type="text/javascript">
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
Templates: {
Fields: {
'Comments': {
'View': function (ctx) {
return String.format('<span title="{0}">{1}</span>', this.FieldTitle, ctx.CurrentItem.Comments);
}
},
'Name': {
'View': function (ctx) {
return String.format('<span title="{0}">{1}</span>', this.FieldTitle, ctx.CurrentItem.Name);
}
}
}
}
});
It occurs since when filtering/sorting is getting applied the List View is reloaded.
How to hover List Item in SharePoint 2013
The following function could be used for hovering List Item cells in SharePoint 2013:
function hoverListItems()
{
$('tr.ms-itmhover td').hover(
function() {
var $td = $(this);
var $th = $td.closest('table').find('th').eq($td.index());
$td.attr('title',$th.text());
}
);
}
Since in SharePoint 2013 Client-Side-Rendering (CSR) is the default rendering mode, the example below demonstrates how to register hoverListItem function using OnPostRender event
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
OnPostRender: function() {
hoverListItems();
}
});
Note: using the specified technique List Item hover will also work after
sorting/filtering is applied.
References
Introduction to Client-Side Rendering in SharePoint 2013
Tool-Tip Work-around:
The solution I have been using is a simple, non-html solution. I simply create a link to an item; insert it's own address (so that it doesn't go anywhere); then under the new LINK tab type the tip you want in the Description box.
save the page then try mousing over your new link, voilĂ
Hope that helps some!
Fiddle Example
The site has 2 (or more) pages defined in HTML like this:
<div id="page1" data-role="page">
<div data-role="content" id="page1-content">
Next Page
</div>
</div>
<div id="page2" data-role="page">
<div data-role="content" id="page2-content">
Go Back
</div>
</div>
In Javascript - I am at once initializing everything:
$(function(){
$('#page1-content, #page2-content').each(function(){
var ul = $('<ul>');
$.each(['one','two','three'], function(item){
ul.append('<li>'+item+'</li>');
});
ul.appendTo(this).listview();
});
});
But the page only initializes the list view on the 1st page, and the list view on the 2nd (and not currently visible) page does not initialize and gives me an error.
Cannot read property 'jQuery19107783124386332929' of undefined
What am I doing wrong?
I really want to be able to start fetching the data and at least create the DOM in every page at once in the beginning, rather than waiting till the user clicks "Next Page".
Also - the list view on the first page is overlapping the "Next" button. I see this overlapping often and would like to fix/understand this too.
Page data-role=page in jQuery Mobile passes through different stages, as shown in the diagram below.
Image / diagram source: http://bradbroulik.blogspot.co.nz/2011/12/jquery-mobile-events-diagram.html
Enhancing widgets manually should be called on active page only, otherwise it will result in a error.
To do add fresh elements on different pages, you need to do this when pagecreate or pagebeforecreate events occur, without the need to call any enhancement method. As the widget will be auto-initialized/enhanced during that stage.
Also, you have a mistake in your code where you didn't close ul tag. However, this didn't cause the error.
The below code shows how to add elements to different pages without the need to call any enhancement method manually.
$(document).on("pagecreate", "[data-role=page]", function (e) {
var ul = $('<ul data-role="listview" data-inset="true"></ul>'),
html = '';
$.each(['one', 'two', 'three'], function (item) {
html += '<li>' + item + '</li>';
});
ul.append(html);
$('[data-role=content]', this).append(ul);
});
Demo
This might be a particular issue of the versions combination you're using, but before checking that, I would try delegating on the 'pageinit' event instead of the regular document ready.
Please try this:
$(document).on('pageinit',function(){
$('#page1-content, #page2-content').each(function(){
var ul = $('<ul>');
$.each(['one','two','three'], function(item){
ul.append('<li>'+item+'</li>');
});
ul.appendTo(this).listview();
});
});
I have an Ember.js app that has a Checklist model, where each Checklist has many Checkitems. (A variant of the classic ToDo app, but with multiple TodoLists.)
In the top-most view, the user sees a listing of all available checklists to the left. When a checklist is selected, the corresponding checkitems appear to the right.
The checkitems on the right side are drag sortable. I'm using this html5sortable library to handle drag sorting. It's like the classic jQueryUI version, but less clunky.
Upon initial loading of the app, the sortable list works fine. However, if the list of checkitems changes (either because a checkitem is marked as complete, a new checkitem is added, changes to an existing check item are saved, or another checklist is selected on the left), the binding to html5sortable is lost.
When the app first loads, I have a view called App.CheckitemsPendingTableView:
App.CheckitemsPendingTableView = Ember.View.extend({
templateName: 'app/templates/checkitems/checkitemsPendingTable',
classNames: ['checkitems-list', 'sortable', 'list'],
tagName: 'ul',
didInsertElement: function() {
this._super();
$('ul.sortable').sortable();
console.log('CheckitemsPendingTableView has been inserted in the DOM and is bound to sortable. At this point, drag-sorting of the list is working fine.');
}
});
The corresponding template is called checkitemsPendingTable.handlebars and it looks like this:
{{#each content}}
{{view App.CheckitemSingleView checkitemBinding="this"}}
{{/each}}
And for good measure, the controller that feeds the content attribute for that view is App.checkitemsController.remainingItems:
App.checkitemsController = Ember.ArrayProxy.create({
content:[],
...snip...
remainingItems: function() {
var checkitems = this.get('content');
var sortedCheckitems = checkitems.filterProperty('isDone', false).sort(function(a,b) {
return a.get('position') - b.get('position');
});
return sortedCheckitems;
}.property('content.#each.isDone'),
...snip...
});
The content attribute of the checkitemsController is driven by the checklistsController:
App.checklistsController = Ember.ArrayProxy.create({
content: App.store.findAll(App.Checklist),
selectedChanged: function() {
var checklist = this.get('selected');
var checkitems = checklist.get('checkitems');
App.checkitemsController.set('checklist', checklist);
App.checkitemsController.set('content', checkitems);
}.observes('selected')
});
(You may have noticed that this controller pulls its data from a Rails backend via ember-data. This shouldn't matter for the current issue, though.)
The view for the left-hand side's menu is called checklistsView. It has a child view called checklistSingleView that is rendered for each of the checklists:
App.ChecklistSingleView = Ember.View.extend({
templateName: 'app/templates/checklists/checklistSingle',
classNames: ['menu-item'],
tagName: 'tr',
...snip...
chooseList: function() {
var checklists = App.checklistsController.get('content');
checklists.setEach('isActive', false);
var checklist = this.get('checklist');
checklist.set('isActive', true);
App.checklistsController.set('selected', checklist);
}
...snip...
});
And, finally, the corresponding template checklistSingle.handlebars contains a link that is tied to the chooseList by way of an action:
<a href="#" {{action "chooseList"}}>{{checklist.name}}</a>
So, everything above works brilliantly...until the user causes a change to the ul of checkitems on the right. At that point, the binding to html5sortable is lost, and I cannot find a convenient place to refresh it.
The problem is that didInsertElement is not called again for the view that generates that ul (i.e., CheckitemsPendingTableView). When the checklistController's content attribute changes, the child views dutifully adjust to reflect the currently-selected list of checkitems. However, the original binding to sortable() is lost, and there is no apparent hook for re-binding to sortable() via jQuery.
I can't re-bind on the child view of CheckitemsPendingTableView, since that would repeat for every instance of a checkitem in the currently-selected list. I can't rebind from the controllers or models, since they will attempt to bind before the DOM update is completed.
I'm sure I'm just thinking about this incorrectly. I'm new to Ember.js (if it isn't wildly obvious), and am struggling to understand how this case is properly handled.
UPDATE
I solved this problem, by adding the following function and observer to the App.CheckitemsPendingTableView:
resetSortableTable: function() {
$('.sortable').unbind('sortable');
$('.sortable').sortable();
console.log('Sort reset by CheckitemsPendingTableView');
},
itemsChanged: function() {
console.log('itemsChanged caught in CheckitemsPendingTableView');
// flush the RunLoop so changes are written to DOM
Ember.run.sync();
Ember.run.next(this, function() {
this.resetSortableTable();
});
}.observes('content.#each')
I based my solution on this answer. I'm a little worried that it's not a great solution, since it seems to be making assumptions about the DOM completing during a run loop iteration.
Whoa! Kind of detailed question...
I think your issue comes from html5sortable plugin: it uses JQuery's bind method to attach handlers directly on living elements. As those elements disappear/evolve under Ember control, the binding are lost.
The plugin should better use JQuery's on method to bind handlers, as specified in the documentation.