--Before I begin, I should say I'm trying to accomplish this with no libraries. Sorry jQuery--
In a general sense, I'm wondering the best way to use event listeners on a list when no item in the list has a unique classname or id. More specifically, I have this:
Currently, I have a WinJS listView that is data-bound like in this guide. As you can see, no item in the list has a unique element id or classname. This is causing me an issue because I want to add a click listener to each one, so that when you click a certain item in the list, this behavior happens (as shown in the gif). I need a sub menu to appear underneath the item in the list that corresponds to the appropriate item in the list.
This would be easy enough if they all had unique ids and/or classnames. But since they don't, I was thinking when somebody clicks on an item, you have to get which one it is and then add a classname to all the items in the list beneath the one you clicked so that you can move them down using the WinJS .createExpandAnimation function. Then once the submenu has been closed, strip off all of the added classnames. Is that really the best way to do it?
I'm open to completely removing the listView, but I'd like a way to data-bind in a similar fashion. Is there a way to do this not using the listView (or another library) that would make life easier?
Edit:
My code is in an identical format to this that Microsoft provides in their walkthrough.
<div id="mediumListIconTextTemplate" data-win-control="WinJS.Binding.Template">
<div style="width: 150px; height: 100px;">
<!-- Displays the "picture" field. -->
<img src="#" style="width: 60px; height: 60px;"
data-win-bind="alt: title; src: picture" />
<div>
<!-- Displays the "title" field. -->
<h4 data-win-bind="innerText: title"></h4>
<!-- Displays the "text" field. -->
<h6 data-win-bind="innerText: text"></h6>
</div>
</div>
</div>
<div id="basicListView" data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource : DataExample.itemList.dataSource,
itemTemplate: select('#mediumListIconTextTemplate')}">
</div>
where dataSource is defined like this:
(function () {
"use strict";
var dataArray = [
{ title: "Basic banana", text: "Low-fat frozen yogurt", picture: "images/60banana.png" },
{ title: "Banana blast", text: "Ice cream", picture: "images/60banana.png" }
];
var dataList = new WinJS.Binding.List(dataArray);
// Create a namespace to make the data publicly
// accessible.
var publicMembers =
{
itemList: dataList
};
WinJS.Namespace.define("DataExample", publicMembers);
Honestly, I'm not even sure this will work with a WinJS listView, so I may have to change this entirely to use an html list or something like that.
You can use the oniteminvoked event (http://msdn.microsoft.com/en-us/library/windows/apps/br211827.aspx) to handle the click of a ListView item, and the clicked item will be avaialble in the event handler's parameter.
What you do with that value is up to you. Use it to insert an item in the ListView, use it to show a hidden element in the invoked item, etc.
Related
I have a problem that has left me scratching my head for days. My knowledge of JavaScript is not great, I'm still learning, and to top it off I'm working on a project where I'm forced to use handlebars, Marionette and other stuff I'm not familiar with.
I have a handlebars template which looks like this:
{{#if images}}
{{#each images}}
<div class="image-thumbnail">
<i class="delete-button" style="cursor:pointer" id="delete-button-id" data-imgid="{{id}}"></i>
<img src="{{thumbnail}}"/></a>
</div>
{{/each}}
This all looks fine when the page loads, no problems there. If I put {{id}} between <i>{{id}}</i> then the value is output correctly to the browser. The problem I'm having is accessing that id from a pop-up which I'm generating using:
deleteImage: function(event) {
new DeleteView({model: new Backbone.Model()}).render().$el.appendTo('body');
},
I've tried adding the following:
new DeleteView({model: new Backbone.Model({ imgid: imageID })}).render().$el.appendTo('body');
And setting imgid using:
var imageElement = document.getElementById('delete-button-id');
var imageID = imageElement.getAttribute('data-imgid');
Unfortunately this only gets the last imgid and it's the same for every one. The page is basically a list of photos which are generated from the handlebars loop. There can be dozens on the page at once, and the imgid I get when the pop-up fires needs to be specific to the one I clicked.
My main view for the page where the images appear:
var ThumbnailView = Marionette.ItemView.extend({
template: getTemplate('profile/thumbnail'),
className: 'main-gallery',
events: {
'click .delete-button': 'deleteImage'
}
... more code follows ...
Can anyone tell me what I'm doing wrong? I need to get the correct dynamically-generated imgid from data-imgid="{{id}}" but I'm only getting the same one each time.
Any help would be greatly appreciated.
Thanks in advance!
The reason why you're getting the last ID each time is because you are selecting the element via the ID attribute:
document.getElementById('delete-button-id');
and unfortunately, your template has id="delete-button-id" on each of the <i> elements being rendered, thus when you try to do document.getElementById it will always return the last element with that attribute. The id attribute (unlike class) is intended to be unique, and according to W3C it must be unique in the document. If it isn't, it can cause issues like the one you're seeing (see here for more info: https://developer.mozilla.org/en-US/docs/Web/API/Element/id, https://www.w3.org/TR/html401/struct/global.html#h-7.5.2).
To fix this, remove the id attribute from the <i>. Also remove the dangling </a> as well. Your template should now look like:
{{#each images}}
<div class="image-thumbnail">
<i class="delete-button" style="cursor:pointer" data-imgid="{{id}}"></i>
<img src="{{thumbnail}}"/>
</div>
{{/each}}
Now when you're trying to select the element in deleteImage, you can do so via jQuery/Marionette by doing:
var imageId = this.$(event.currentTarget).data('imgid');
Or if you're keen on using plain JavaScript, you can obtain the ID like so:
var imageId = event.currentTarget.getAttribute('data-imgid');
And that should do it. You can play around with this JSBin to see how it will work:
https://jsbin.com/fotiqisoye/edit?html,js,output
the marionette way to restructure this solution would be to replace your each loop with a CompositeView, and feed it a collection of models, which will each render an ItemView, which will be represented in your dom as a single line-item of image.
when you click delete on that image, the ItemView receives the event, and the model.id passed into it will be correct.
more on this here: http://marionettejs.com/docs/v2.4.7/marionette.compositeview.html
hope this helps
I currently have a slideshow run with HTMl CSS and JS at the moment for the navigation buttons below the slideshow it is just placing numbers instead of text. Is there a way to grab the images title and use it or custom text for each slide link. Below i included the Javascript that makes the navigation buttons and adds the text. If you need anything else just let me know.
If i can just specify text in this JS file that would work too.
Also if it may help im using Kickstart HTML Template.
Link to view it http://bliskdesigns.com/clients/timbr/
var items = $(this).find('li');
wrap.append('<ul class="slideshow-buttons"></ul>');
items.each(function(index){
wrap.find('.slideshow-buttons')
.append('<li>'+(index+2)+'</li>');
});
It's difficult to give you a concise answer with what you've provided, however:
Assuming that in the code you've provided:
items is an array containing each slide in your slideshow;
That each element in items contains an img;
That the text that you want to appear in the slideshow nav is the title attribute of each img;
That the unordered list being built by wrap is the navigation;
That the text you want to change is the numeral within the anchor of each item injected into wrap.
Here's a potential answer:
// put all slides into 'items'
var items = $(this).find('li');
// append the wrap element with an unordered list
wrap.append('<ul class="slideshow-buttons"></ul>');
// loop over each item
items.each(function(index){
// grab the title attribute of the img child of this instance of items
var titleText = $(this).find('img').attr('title');
// push a new <li> into 'wrap'
wrap.find('.slideshow-buttons').append('<li>'+titleText+'</li>');
});
This should just be a direct replacement for wherever in your project the code you've included above came from.
As I say: I can't promise that this will work without a lot more information, but in theory it will. Make sure that each of your images has a title:
<img src="/link/to/image.kpg" alt="alternative text" title="text you want to appear in the slider navigation" >
Alternatively, you can use the text in the image's alt tag instead by changing this line from above:
// grab the alt attribute of the img child of this instance of items
var titleText = $(this).find('img').attr('alt');
In the list items you can add the text that you want to display instead of numbers, as shown below.
<li data-displayText="Timber Mart"> <img src="http://i.imgur.com/4XKIENA.png" width="920" /> </li>
Then in above code you can use the text instead of index, as shown below.
wrap.find('.slideshow-buttons')
.append('<li>'+$(items[index]).attr("data-displayText")+'</li>');
I have a tweet stream where new tweets are added at the top and the older ones pushed down. You can click on the entire tweet and a panel slides down to reveal, "reply", "retweet", "favorite" etc. The panel is added to each new tweet added in the stream.
The code below works. Shouldn't this be better written so that only one call is being made? Or, as a new tweet is added. would I just have to add to the code with div#tc4, ul#tb4 etc?
$(document).ready(function () {
$("div#tc1").click(function () {
$("ul#tb1").slideToggle("fast");
});
$("div#tc2").click(function () {
$('ul#tb2').slideToggle("fast");
});
$("div#tc3").click(function () {
$('ul#tb3').slideToggle("fast");
});
});
Added Markup:
<div id="tc1" class="tweetcontainer">
<div class="avatarcontainer">
<div class="avatar"></div>
</div>
<div class="content">
<div class="tweetheader">
<div class="name">
<h1>John Drake</h1>
</div>
<div class="tweethandle">
<h2>#Drakejon</h2>
</div>
<div class="tweettime">10m</div>
</div>
<div>
<p>Exceptional Buys Ranger To Give Monitoring Shot In The Arm To Its 'DevOps' Platform http://tcrn.ch/11m3BrO by #sohear </p>
</div>
</div>
</div>
<!-------------Tool Bar -------------------------------->
<ul id="tb1" class="toolbar">
<li><a class="reply" href="#"><span>reply</span></a></li>
<li><a class="retweet" href="#"><span>retweet</span></a></li>
<li><a class="favorite" href="#"><span>favorite</span></a></li>
<li><a class="track" href="#"><span>track</span></a></li>
<li><a class="details" href="#"><span>details</span></a></li>
</ul>
I highly recommend separating your javascript from your detailed page function. The best way to do this is to put the retweeting panel inside the tweet container, then you don't even need to give it an id at all or encode in the javascript information about your html structure and ids. You can then just do:
$('.tweetcontainer').on('click', function(event) {
if ($(event.target).is(':descendantof(.toolbar)')) {
//ignore all clicks within the toolbar itself
return;
}
$(this).find('.toolbar').slideToggle();
});
It's that easy! See it in action in a jsFiddle.
Now you can add as many tweet containers as you want to your page--and your javascript doesn't have to change one bit. Other solutions that require knowledge of specific ids linking to specific ids are suboptimal.
Note the descendantof pseudo-selector is custom (see the fiddle to find out how it works). Also, since you didn't provide any css, I had to choose some--it was quick so don't expect much. (Aww heck I just saw you updated your question to provide a jsFiddle with css giving a far prettier result--but I won't change mine now.) I did have to add a class to the actual tweet itself, but there is probably a better way to style it.
And if you want a click on the displayed toolbar itself (outside of a link) to allow collapsing the toolbar, change the code above to :descendantof(a).
If you don't want to change your page layout, another way to it is to encode the information about the linkage between html parts in the html itself using a data attribute. Change your tweetcontainer div to add a data attribute with a jQuery style selector in it that will properly locate the target:
<div class="tweetcontainer" data-target="#tb1">
You don't really have to remove the id if you use it elsewhere, but I wanted you to see that you don't need it any more. Then on document.ready:
$('.tweetcontainer').click(function () {
$($(this).data('target')).slideToggle('fast');
});
Here is another jsFiddle demonstrating this alternate technique (though it less elegant, in my opinion).
Last, I would like to mention that it seems possible you have a little bit of "div-itis". (We have all been there.) The toolbar anchor elements have unnecessary spans inside of them. The tweet name h1 element is inside a div, but could just be an h1 with class="name" instead.
In general, if there is only a single item inside a div and you can change your stylesheet to eliminate the div, then the div isn't needed. There are an awful lot of nested divs in your html, and I encourage you to remove as many of them as you can. Apply style to the other block elements you use and at least some, if not many, won't be needed.
I'd suggest (though currently untested):
$('div[id^="tc"]').click(function(){
var num = parseInt(this.id.replace(/\D+/g,''),10);
$('#tb' + num).slideToggle("fast");
});
Though given that you don't need the num to be a number (it'd be fine as a string), you could safely omit the parseInt().
Yes, you can write this code much more compactly like this:
$(document).ready(function() {
for (var i = 1; i < 3; i++) {
$("div#tc" + i).click(function() { $("ul#tb" + i).slideToggle("fast"); } );
}
});
I am using a WinJS.UI.ListView in a JavaScript project for Windows-8 Metro interface.
This is the HTML code:
<div id="myMain">
<div id="myListTemplate" data-win-control="WinJS.Binding.Template">
<div class="myListViewItem">
<p data-win-bind="innerText: description"></p>
</div>
</div>
<div id="myListView"
data-win-control="WinJS.UI.ListView"
data-win-options="{ itemTemplate: myTemplate, layout: {type: WinJS.UI.ListLayout} }">
</div>
</div>
And part of the JavaScript:
ready: function (element, options) {
// Set up the ListView.
var myLView = myListView.winControl;
WinJS.UI.setOptions(favoritesLView, {
itemDataSource: dataSource,
oniteminvoked: this.onItemInvoked.bind(this),
selectionMode: WinJS.UI.SelectionMode.none
});
},
Where dataSource is where the list information is stored. It is a list obtained from:
var myList = new WinJS.Binding.List(myJSONarray);
The code is working in landscape and portrait view, but doesn't work when in snapped view. The list view appears empty despite myList has all the elements.
Is this a bug in Windows 8? Anybody know of a workaround to solve it?
I have found this link with the same problem but its solution doesn't work for me:
http://social.msdn.microsoft.com/Forums/pl-PL/winappswithhtml5/thread/ce8c722d-526b-4226-9e40-642ddb37422b
One reason that the ListView will not be displayed is if the element or any parent elements' display is set to "none". When the element becomes visible again, you will need to call listView.forceLayout(). See the explanation below from MSDN, http://msdn.microsoft.com/en-us/library/windows/apps/hh758352.aspx
When you set the style.display property of a ListView or its parent
elements to "none", the app discards layout information about those
elements. When you change the value of the display property back to a
style that makes everything visible again, the app will layout all the
elements, but the ListView won't receive information about the current
layout and will have an invalid layout. You can fix the issue by
calling this function when you make the ListView visible again.
The Situation
In Sharepoint 2010 I can click on an item in a list:
And then the Read/Edit view becomes visible in that page:
My Goal
I have a WebPart on another Page where I show some items coming from this and several other lists and I want to add a read or edit link to each of them.
How can I do that?
I'm searching for a function like EditListItem('ItemId', 'ListId', ...) which will open the edit div window.
What have I tried
The a tag generated by Sharepoint on "Test Item" above is like this:
<a onfocus="OnLink(this)"
href="http://{mysharepointsite}/_layouts/listform.aspx
?PageType=4
&ListId={D0FDB54F-1DDF-4C5E-865B-ABDE55C1125}
&ID=1
&ContentTypeID=0x010800ED5176D13CCEFC4AA8D62A79985DE892"
onclick="EditLink2(this,49);return false;" target="_self">Test Item</a>
So I digged a bit into the Sharepoint JS files and found EditLink2 calling _EditLink2 which calls ShowPopup from the context (the 49) is the context no and seems to be dynamic.
I tried to fake the context but there are billions of variables and I think I can't get that to work stable.
On that page where you need to open dialog just write simple JS function for showing modal dialog, for example:
function openMyItemDialog( itemId ) {
var options = {
url: "http://{mysharepointsite}/_layouts/listform.aspx?PageType=4&ListId={D0FDB54F-1DDF-4C5E-865B-ABDE55C1125}&ID=" + itemId + "&ContentTypeID=0x010800ED5176D13CCEFC4AA8D62A79985DE892&IsDlg=1",
width: 500,
height: 500,
title: "Item view/edit"
};
SP.UI.ModalDialog.showModalDialog( options );
}
Note the &IsDlg=1 param at url
And then modify href link where you display your items.
For example:
Test item
Replace 35 to ID of your item
I know this is an old question, but there's another way to achieve what the OP was trying to do.
In a XSLT ViewWebPart, there is a global parameter named $ViewCounter.
This is the context number required by the _EditLink2 function.
So, in order to add a link to the display form and have it opened in a dialog, wrap the item in an <a> tag like this:
your item
Notice the variables $HttpVDir, $List, $thisNode/#ID and $ViewCounter (no need to hard-code any value).