Well, I'm pulling my hair out with an error grouping items to render in a listview.
When I set the items/groups datasources to the list thru WinJS.UI.setOptions I get an error in ui.js at line 16812 saying "Unable to get property 'firstItemIndexHint' of undefined or null reference".
I have other pages in my application where grouping works fine but this one doesn't seems to work. I use the same binding method in all of them and I can't find any difference between them that causing this error.
Can anyone tell me what's making the following code crash?.
Thanks.
javascript page code in ready function:
var arr = new Array();
//fill in an array with sample data.
//name property is the one used for grouping
//nm is rendered in the item template.
for (var i = 0; i < 10; i++) {
arr.push({
name: "group" + (i % 5),
nm: "namer" + i,
});
}
//create a list based on the array.
var items = new WinJS.Binding.List(arr);
//group items by the name property (key selector function) and
//use it for rendering the group template (group data function)
var groupDataSource = items.createGrouped(
function (item) {
return item.name;
},
function (item) {
return {
name: item.name,
click: function () {
nav.navigate("mynavpage.html");
}
};
});
//get a reference to the listview control
var listView = element.querySelector(".itemGroups").winControl;
//set templates and datasources to listview
//here's where calls to base.js/ui.js are made and where app fails.
WinJS.UI.setOptions(listView, {
groupHeaderTemplate: $(".headerTemplate")[0],
itemTemplate: $("#itemTemplate1")[0],
itemDataSource: items.dataSource,
groupDataSource: groupDataSource.groups.dataSource,
});
html page code
<!-- group header template -->
<div class="headerTemplate" data-win-control="WinJS.Binding.Template" style="display: none">
<h2 class="group-title" data-win-bind="textContent: name" role="link">
</h2>
</div>
<!-- item template -->
<div id="itemTemplate1" data-win-control="WinJS.Binding.Template" style="display: none">
<div class="itemTemplate item1x1" id="item1">
<div class="itemText">
<h4 class="itemTitle win-type-ellipsis" data-win-bind="textContent: nm"></h4>
</div>
</div>
</div>
<!-- fragment section -->
<div class="myPage fragment">
<header aria-label="Header content" role="banner">
<button class="win-backbutton" aria-label="Back" disabled></button>
<h1 class="titlearea win-type-ellipsis">
<span class="pageTitle">Page Title</span>
</h1>
</header>
<section aria-label="Main content" role="main">
<!-- listview Grid -->
<div class="itemGroups" aria-label="List of groups" data-win-control="WinJS.UI.ListView"
data-win-options="{ tapBehavior: 'toggleSelect' }">
</div>
</section>
</div>
You need to use the groupDataSource for both the items and the groups.
WinJS.UI.setOptions(listView, {
groupHeaderTemplate: $(".headerTemplate")[0],
itemTemplate: $("#itemTemplate1")[0],
itemDataSource: groupDataSource.dataSource,
groupDataSource: groupDataSource.groups.dataSource,
});
Related
I have an array of objects which i populate on a button click.
When populating this array i make sure that i only add 10 objects to it.
When this is all loaded in the dom i give the user the oppertunity to add a few more objects.
I do this like this:
$scope.Information = [];
$.each(data, function (i, v) {
if (i<= 9)
$scope.Information.push(data[i]);
if(i >= 10) {
cookieList.push(data[i]);
}
}
if (cookieList.length) {
localStorage.setItem("toDoList", JSON.stringify(cookieList));
$(".showMore").removeClass("hidden");
}
$(".showMore").on("click", function() {
var obj = JSON.parse(localStorage.getItem("toDoList"));
console.log(obj);
console.log(obj.length);
SetSpinner('show');
$scope.Information.push(obj);
SetSpinner('hide');
//$.removeCookie("toDoList2");
});
part of the HTML:
<div ng-repeat="info in Information" class="apartment container" style="padding-right:35px !important">
<div class="row" style="height:100%">
<div class="col-md-1 col-xs-12">
<div>
<h4 class="toDoListHeadings">Nummer</h4>
<div style="margin-top: -15px; width:100%">
<span class="toDoListItems number">
{{info.orderraderid}}
</span>
</div>
</div>
</div>
</div>
</div>
My issue: When i add objects to my array of objects "$scope.Information.push(obj);" I assumed that they would get added in the DOM but they do not, how do i do this the angular way?
EDIT MY SOLOUTION:
edited the HTML to use ng-click and the method is as follows:
$scope.addMore = function() {
var obj = JSON.parse(localStorage.getItem("toDoList"));
SetSpinner('show');
$.each(obj, function(i,v) {
$scope.Information.push(v);
});
SetSpinner('hide');
}
Here is the angular way:
The view
<!-- Reference your `myapp` module -->
<body data-ng-app="myapp">
<!-- Reference `InfoController` to control this DOM element and its children -->
<section data-ng-controller="InfoController">
<!-- Use `ng-click` directive to call the `$scope.showMore` method binded from the controller -->
<!-- Use `ng-show` directive to show the button if `$scope.showMoreButton` is true, else hide it -->
<button data-ng-click="showMore()" data-ng-show="showMoreButton">
Show more
</button>
<div ng-repeat="info in Information" class="apartment container" style="padding-right:35px !important">
<div class="row" style="height:100%">
<div class="col-md-1 col-xs-12">
<div>
<h4 class="toDoListHeadings">Nummer</h4>
<div style="margin-top: -15px; width:100%">
<span class="toDoListItems number">
{{info.orderraderid}}
</span>
</div>
</div>
</div>
</div>
</div>
</section>
</body>
The module and controller
// defining angular application main module
var app = angular.module('myapp',[])
// defining a controller in this module
// injecting $scope service to the controller for data binding with the html view
// (in the DOM element surrounded by ng-controller directive)
app.controller('InfoController',function($scope){
$scope.Information = [];
$scope.showMoreButton = false;
// Bind controller method to the $scope instead of $(".showMore").on("click", function() {});
$scope.showMore = function(){
var obj = JSON.parse(localStorage.getItem("toDoList"));
console.log(obj);
console.log(obj.length);
SetSpinner('show');
$scope.Information.push(obj);
SetSpinner('hide');
//$.removeCookie("toDoList2");
};
$.each(data, function (i, v) {
if (i<= 9) $scope.Information.push(data[i]);
if(i >= 10) cookieList.push(data[i]);
});
if (cookieList.length) {
localStorage.setItem("toDoList", JSON.stringify(cookieList));
//$(".showMore").removeClass("hidden");
$scope.showMoreButton = true; // use $scope vars and ng-class directive instead of $(".xyz").blahBlah()
}
});
You should not use JQuery, use ng-click to detect the click, because angular has no idea when JQuery is done and when it needs to refresh the interface
I'm working on my first Ember app. It's a variation of a to do app. You type in a value, hit submission button and the page should update with each new item added using two-way data binding.
Every new item gets added to an array of object literals.
So adding new objects to the array and then looping through each item and printing it to the page is working just fine. Only problem is the page never updates with new items added via the input field.
I thought creating a custom view (App.ReRenderUserList in this instance) and adding .observes like they talk about in a previous question might be the answer, but that didn't seem to work.
Here's my code. Let me know if there's anything else I need to add. Thanks for your help.
index.html
<script type="text/x-handlebars" data-template-name="add">
{{partial "_masthead"}}
<section>
<div class="row">
<div class="column small-12 medium-9 medium-centered">
<form {{action "addToList" on="submit"}}>
<div class="row">
<div class="column small-8 medium-9 no-padding-right">
{{input type="text" value=itemName}}
</div>
<div class="column small-4 medium-3 no-padding-left">
{{input type="submit" value="Add +"}}
{{!-- clicking on this should add it to the page and let you keep writing --}}
</div>
</div>
<!-- /.row -->
</form>
</div>
<!-- /.column -->
</div>
<!-- /.row -->
</section>
<section>
<div class="row">
<div class="column small-12 medium-9 medium-centered">
<div class="list">
{{#each userItems}}
<div class="column small-16">
{{#view App.ReRenderUserList}}
<div class="item">{{name}}</div>
{{/view}}
</div>
<!-- /.column -->
{{/each}}
</div>
<!-- /.list -->
</div>
<!-- /.column -->
</div>
<!-- /.row -->
</section>
</script>
<!-- END add items template -->
pertinent app.js code:
var itemLibrary = [
{
'name' : 'bread'
},
{
'name' : 'milk'
},
{
'name' : 'eggs'
},
{
'name' : 'cereal'
}
];
var userLibrary = [];
App.AddRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
presetItems: itemLibrary,
userItems: userLibrary
});
}
});
App.AddController = Ember.ObjectController.extend({
actions: {
// add the clicked item to userLibrary JSON object
addToList: function(){
var value = this.get('itemName'); // gets text input value
userLibrary.push({
name: value // this is just echoing and not adding my new items from the form.
}); // adds it to JSON Object
console.log(userLibrary);
}
}
});
App.ReRenderUserList = Ember.View.extend({
submit: function(){
console.log('rerendered!');
}
});
You should use the pushObject method instead of the push method. This will update the bindings..
App.AddController = Ember.ObjectController.extend({
actions: {
// add the clicked item to userLibrary JSON object
addToList: function(){
var value = this.get('itemName'); // gets text input value
userLibrary.pushObject({
name: value // this is just echoing and not adding my new items from the form.
}); // adds it to JSON Object
console.log(userLibrary);
}
}
});
I have this code that successfully deletes an exam from a list of exams displayed on a page, but the page still shows the deleted exam. You have to manually refresh the page for the view to update. We are using a simlar pattern on other pages and it's working correctly. I don't understand why it doesn't work on this page.
// Used to handle the click event for Delete
remove = (exam: Models.Exam) => {
$("#loadingScreen").css("display", "block");
var examService = new Services.ExamService();
examService.remove(exam.examId()).then(() => {
examService.getByFid().then((examinations: Array<Models.Exam>) => {
this.exams(examinations);
this.template("mainTemplate");
});
}).fail((error: any) => {
// Add this error to errors
this.errors([error]);
window.scrollTo(0, 0);
}).fin(() => {
$("#loadingScreen").css("display", "none");
});
}
Here's the UI code that displays the list of exams
<div class="section module">
<!-- ko if: exams().length > 0 -->
<!-- ko foreach: exams.sort(function(a,b){return a.mostRecentDateTaken() > b.mostRecentDateTaken() ? 1:-1}) -->
<div class="addremove_section bubbled">
<a class="button open_review" data-bind="click: $root.edit">Edit</a>
<a class="button open_review" data-bind="click: $root.remove">Delete</a>
<div class="titleblock">
<h4 data-bind="text: 'Exam Name: ' + examTypeLookup().examTypeName()"></h4>
<div data-bind="if:examEntityLookup()!=null">
<div data-bind=" text: 'Reporting Entity: ' + examEntityLookup().description()"></div>
</div>
<div data-bind="text: 'Most recent date taken: ' + $root.formatDate(mostRecentDateTaken())"></div>
<div data-bind="text: 'Number of attempts: ' + numberOfAttempts()"></div>
<div data-bind="text: 'Pass/Fail Status: ' + $root.PassFailEnum(passFailId())"></div>
</div>
<div class="clearfix"></div>
</div>
<!-- /ko -->
<!-- /ko -->
<!-- ko if: exams().length == 0 -->
<div class="addremove_section bubbled">
<div class="titleblock">
<div>No Exams Have Been Entered.</div>
</div>
</div>
<!-- /ko -->
</div>
EDIT: I discovered that if I remove the sort from this line in the view
<!-- ko foreach: exams.sort(function(a,b){return a.mostRecentDateTaken() > b.mostRecentDateTaken() ? 1:-1}) -->
to
<!-- ko foreach: exams -->
it works! The only problem is that I need the data sorted.
I removed the sorting from the view and did the sorting in the service. Not really sure why I can't sort in the view. I assume it's a knockout.js bug.
<!-- ko foreach: exams -->
[HttpGet]
[Route("api/exam")]
public IEnumerable<TDto> GetApplicantExams()
{
var dtos = GetCollection(() => _examService.GetApplicantExams(UserContext.Fid).OrderBy(e => e.DateTaken));
return dtos.ForEach(t => AddItems(t));
}
It's not a bug in Knockout. Since the sort invocation (as you're doing it) is not under the control of a computed/dependent observable, there's nothing to trigger it to resort. You've basically broken the connection between the UI (or more technically, the bindingHandler) and the ko.observable which KO uses for tracking changes.
I've run into this many times where I work, and the general pattern I use is something like this:
var viewmodel = {
listOfObjects:ko.observableArray(),
deleteFromList:deleteFromList,
addToList:addToList,
}
//in your HTML, you'll do a foreach: listOfSortedObjects (instead of listOfObjects)
viewmodel.listOfSortedObjects = ko.computed(function(){
//Since this is *inside* the change tracking ecosystem, this sort will be called as you would expect
return viewmodel.listOfObjects().sort(yourSortingFunction);
});
//Since you cannot edit a (normal) computed, you need to do your work on the original array as below.
function deleteFromList(item){
viewmodel.listOfObjects.remove(item);//Changing this array will trigger the sorted computed to update, which will update your UI
}
function addToList(item){
viewmodel.listOfObjects.push(item);
}
I have a list view to choose content from
<div id="mediumListIconTextTemplate"
data-win-control="WinJS.Binding.Template"
style="display: none">
<div class="mediumListIconTextItem">
<img class="mediumListIconTextItem-Image" src="img/tegan.jpg" />
<div class="mediumListIconTextItem-Detail">
<h4 data-win-bind="innerText: title"></h4>
<h6 data-win-bind="innerText: text"></h6>
</div>
</div>
</div>
<div id="groupedListView" style="display:none;"
data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource: myData.groupedItemsList.dataSource, tapBehavior: 'toggleSelect',
itemTemplate: select('#mediumListIconTextTemplate'),
groupDataSource: myData.groupedItemsList.groups.dataSource,
groupHeaderTemplate: select('#headerTemplate'),
layout: {type: WinJS.UI.GridLayout}}">
</div>
What i did is, i just made listview 'display:none' and on click add i just made 'display:block'. The code below
$("#groupedListView").css('display', 'block');
var name ='';
var myListView = document.getElementById("groupedListView").winControl;
myListView.forceLayout();
myListView.selection.clear();
myListView.addEventListener("iteminvoked", function (e) {
e.detail.itemPromise.then(function (invokedItem) {
name = invokedItem.data.picture; //name of selected data
alert(name + "\"s defined: " + num + ".", 'i');
$("#groupedListView").css('display', 'none'); //hide the listview
});
}, false);
Its works fine for one time, On 2nd, 3rd... list item selection it fails and shows exception
What is the best way to load listview and access the content..?
P.S: The whole listview and other functions are in single html page
I have a rather sophisticated template for Kendo ListView using knockout-kendo.js bindings. It displays beautifully. My problem is that I need to use the visible and click bindings in parts of the template, but I can't get them to work. Below is a simplified version of my template. Basically, deleteButtonVisible determines whether the close button can be seen, and removeComp removes the item from the array.
<div class='template'>
<div >
<div style='display:inline-block' data-bind='visible: deleteButtonVisible, event: {click: $parent.removeComp}'>
<img src='../../../Img/dialog_close.png'></img>
</div>
<div class='embolden'>#= type#</div><div class='label1'> #= marketArea# </div>
<div class='label2'> #= address# </div>
<!-- more of the same -->
</div>
The view model:
function CompViewModel() {
var self = this;
self.compData = ko.observableArray().subscribeTo("compData");
self.template = kendo.template(//template in here);
self.removeComp = function (comp) {
//do something here
}
}
html:
<div class="row" >
<div class="col-md-12 centerouter" id="compDiv" >
<div class="centerinner" id="compListView" data-bind="kendoListView: {data: compData, template: template}"></div>
</div>
</div>
finally, sample data:
{
type: "Comparable",
marketArea: "",
address: "2327 Bristol St",
deleteButtonVisible: true
},
Take in count that the deleteButtonVisible must be a property on the viewModel linked to the view.You are not doing that right now. The click element can v¡be access from the outer scope of the binding and remove the $parent.He take the method from the viewmodel. Take in count that every thing that you take on the vie must be present on the view model for a easy access.