Cannot call method 'push' of undefined - mvvm

I had this working in a fiddle just fine (which I can't find now), but when I moved it to VS2012 I'm getting the error "Cannot call method 'push' of undefined'.
In the code below, the createItemDiv() function works great and creates the UI elements. It's the line before that, showItem() which calls this.visibleItem.push() that is throwing the error. I had a problem with this in the fiddle originally and added the "this" to fix it. If I remove "this" as it is now, I get "visibleItem is not defined".
viewmodel.js
var dummyResults = [
{ //sample data is here }
]
var dummyItems = [
]
function VisibleItem(data) {
var self = this;
this.name = ko.observable(data.name);
this.type = ko.observable(data.type);
this.description = ko.observable("");
}
function SearchResult(data) {
var self = this;
this.name = ko.observable(data.name);
this.type = ko.observable(data.type);
}
var viewModel = {
searchResult: ko.observableArray(ko.utils.arrayMap(dummyResults, function (item) {
return new SearchResult(item);
})),
visibleItem: ko.observableArray(ko.utils.arrayMap(dummyItems, function (item) {
return new VisibleItem(item);
})),
showItem: function (item) {
this.visibleItem.push(item);
}
};
ko.applyBindings(viewModel);
site.js
$(document).on('click', '.result', function () {
var item = ko.dataFor(this);
viewModel.showItem(item); //add item to "visibleItems" viewmodel for management
createItemDiv(item); //ui function to show item on screen
});

try this :
var viewModel = function() {
var self = this;
self.searchResult = ko.observableArray(ko.utils.arrayMap(dummyResults, function (item) {
return new SearchResult(item);
})),
self.visibleItem = ko.observableArray(ko.utils.arrayMap(dummyItems, function (item) {
return new VisibleItem(item);
})),
self.showItem = function (item) {
self.visibleItem.push(item);
}
};

I just realized I had an old binding in my html still... will vote to close.

Related

Resolving Promise Angular 2

I have the following problem.
In a function I have a promise as a return type. This function is in the class Hierarchy.
updateNodeValues(entity: String, data: {}): Promise<any>{
let jsonBody = JSON.stringify(data);
let url = environment.endpointCore + '/api/' + entity + '/' + data['id'];
return this.http.put(url, jsonBody, this.options)
.toPromise()
.then(response => {
return response;
})
.catch(this.handleError);
}
This function is in class node.
onSubmit(): void{
var currentForm = this.form.value;
var entityName = this.inflection.classify(this.node.type).toLowerCase();
var requiredData = {};
for(var i = 0; i < this.formItems.length; i++){
this.formItems[i].value = currentForm[Object.keys(currentForm)[i]];
}
for(var i=0; i<this.formItems.length; i++){
requiredData[this.globalService.camelize(this.formItems[i].label)] = this.formItems[i].value
}
Promise.resolve(this.hierarchyService.updateNodeValues(entityName, requiredData)).then(response => {
alert(response.ok);
if(response.ok){
this.globalService.showSuccessMessage('Values updated');
this.refreshGui(requiredData);
}
});
this.editMode = false;
}
The problem is that when i try to resolve promise and invoke this.refreshGui(requireddata) nothing is happening. I have read about how the fat arrow is preserving the 'context' of this, and I do not understand why invoking this method is not doing anything, while invoking successMessage produces expected outcome.
The method that I am invoking looks like this, and it is also in the class node.
private refreshGui(data: {}){
this._node.data = data;
this.objectProperties = new Array();
this.nodeChildren = new Array();
for (var property in data) {
var propertyValue = data[property];
if (propertyValue instanceof Array) {
this.nodeChildren.push({label: property, value: "total: ".concat(propertyValue.length.toString())});
} else {
this.objectProperties.push({label: property, value: propertyValue});
}
}
}
The solution that I found to be working was to implement custom event. The problem was that within the async callback resolution, the context of what this is would "get lost". The fat arrow enabled me to invoke class method with this, but the properties within the would be "lost". Because of this reason I have took the logic from the method, and put it in the callback part and set expected and needed results in some variable. This variable was passed to my custom event and set to class variable in the custom event handler appropriately.

TypeScript class decorator that modifies object instance

I'm making a plugin for Aurelia and need a class decorator that
adds attributes to the new object instance, and
calls an external function with the new object as an argument.
I've looked through examples, and so far I've put together ("pseudo-ish" code)
return function addAndCall(target: any): any {
var original = target;
var newConstructor = function (...args) {
original.apply(this, args);
this.newAttribute = "object instance value";
ExternalModule.externalFunction(this);
};
newConstructor.prototype = Object.create(original.prototype);
newConstructor.prototype.constructor = original;
return <any>newConstructor;
}
but
I'm not entirely clear on the details here (or what is actually needed), and
it might not work properly since I'm getting Aurelia errors when using objects instantiated from classes with this decorator (and I suspect it's my decorator rather than the Aurelia framework that's buggy).
Any help and explanation would be greatly appreciated!
Why not just assign those properties to the prototype, and subsequently assign to the instance on first invocation
// decorator
function addAndCall(cb: Function, newField: string) {
// cb is now available in the decorator
return function(ctor: Function): void {
Object.defineProperty(ctor.prototype, newField, {
value: function(...args: any[]) {
return Object.defineProperty(this, newField, {
value: function(...args: any[]) {
console.log(newField, ...args);
}
})[newField](...args);
}
});
cb(ctor);
}
}
let callMe = (decoratedCtor) => console.log(decoratedCtor);
#addAndCall(callMe, 'propertyName')
class AddToMe {}
let addToMe = new AddToMe();
(<any>addToMe).propertyName(1, 2);
Here's a working version:
function addAndCall(target: any) {
var original = target;
function construct(constructor, args) {
var c: any = function () {
this.newAttribute = "object instance value";
ExternalModule.externalFunction(this);
return constructor.apply(this, args);;
}
c.prototype = constructor.prototype;
return new c();
}
var f: any = function (...args) {
return construct(original, args);
}
f.prototype = original.prototype;
return f;
}
(code in playground)

TYpescript : Static methods on Function as class

I have a fn that inherit an existing fn ( take Angular1 $q for example )
//$q original behavior
var defer = $q.defer();
defer.promise.then(function(result){})
//or
$q( (resolve, reject) => {
//promise execution here
}).then(function(result){});
If I want to decorate it, I would do :
var Qdecorator = function($delegate) {
var Q = function(resolver:any): any {
//do some extra stuff here
return $delegate.apply($delegate, arguments);
}
//Assign the static methods here:
Q.defer = function() {
//do some stuff
return $delegate.defer.apply($delegate, []);
}
//same goes for race, when, resole reject and so on
return Q;
}
Problem is that typescript complains about
Property defer, race, when, resolve, etc... does not exist on type '(resolver: any) => any'
I tried to use the IQService, and IPromise with no luck, btu I'd like to raise a more global question :
How do I define late static methods on function() that return an object without using new
I am copying pasting the answer to my question from this link:
https://www.typescriptlang.org/docs/handbook/interfaces.html
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

NativeScript - how can I filter an observable array with SearchBar?

Hi I'm trying to filter an observable array of data fetched via a HTTP request on keypress of the SearchBar.
I managed to get the SearchBar property change to work but I can't seem to figure out what I'm doing wrong in the filtering logic.
Ideally I want to update the list as I type in the search term in the SearchBar. I've searched the API on the Telerik site, there wasn't really any examples I could find.
XML
<Page loaded="pageLoaded">
<ActivityIndicator busy="{{ isLoading }}" />
<ActionBar title="People">
</ActionBar>
<GridLayout>
<StackLayout>
<SearchBar id="searchBar" hint="Search for someone"></SearchBar>
<ListView items="{{ peopleList }}" itemTap="showDetail">
<ListView.itemTemplate>
<StackLayout>
<Label text="{{ fullName }}" horiztonalAlignment="left" verticalAlignment="center"></Label>
<Label text="{{ company }}" class="info"></Label>
</StackLayout>
</ListView.itemTemplate>
</ListView>
</StackLayout>
</GridLayout>
</Page>
JS
var frames = require("ui/frame");
var Observable = require("data/observable").Observable;
var PeopleListViewModel = require("../../shared/people-viewModel");
var activityIndicatorModule = require("ui/activity-indicator");
var page;
var userkey;
var peopleList = new PeopleListViewModel([]);
var pageData = new Observable({ peopleList: peopleList });
exports.pageLoaded = function(args) {
page = args.object;
page.bindingContext = pageData;
userkey = userkey || page.navigationContext.userkey;
peopleList.load(userkey); // fetch data from the backend
var searchBar = page.getViewById("searchBar");
searchBar.on("propertyChange", function (args) {
var searchText = args.object.text;
if (searchText === "") {
// NOT SURE WHAT TO DO HERE.
} else {
peopleList.filter(function (element, index, array) {
// DOESN"T WORK PROPERLY
console.log("element: ", JSON.stringify(element));
return element.fullName == searchText;
});
console.log("Text types: ", searchText);
}
});
};
exports.showDetail = function(args) {
var person = peopleList.getItem(args.index);
var navigateEntry = {
moduleName: "views/people/people-detail",
context: { person: person },
animated: false
};
frames.topmost().navigate(navigateEntry);
};
PeopleListViewModel.js
var config = require("./config");
var fetchModule = require("fetch");
var ObservableArray = require("data/observable-array").ObservableArray;
function PeopleListViewModel(people) {
var viewModel = new ObservableArray(people);
viewModel.load = function (userKey) {
return fetchModule.fetch(config.baseUrl + "/api/people/all/" + userKey)
.then(function (response) {
return response.json();
})
.then(function (data) {
data.forEach(function (person) {
viewModel.push(person);
});
}, function (error) {
console.log("Error: ", error);
});
};
viewModel.empty = function () {
while (viewModel.length) {
viewModel.pop();
}
};
return viewModel;
}
function handleErrors(response) {
if (!response.ok) {
console.log("Error occurred");
}
}
module.exports = PeopleListViewModel;
Updated people-list
var frames = require("ui/frame");
var Observable = require("data/observable").Observable;
var ObservableArray = require("data/observable-array").ObservableArray;
var PeopleListViewModel = require("../../shared/people-viewModel");
var activityIndicatorModule = require("ui/activity-indicator");
var page;
var userkey;
var peopleList = new PeopleListViewModel([]);
var pageData = new Observable({ peopleList: peopleList });
var resultList = new ObservableArray([]);
exports.pageLoaded = function(args) {
page = args.object;
page.bindingContext = pageData;
userkey = userkey || page.navigationContext.userkey;
peopleList.load(userkey);
var searchBar = page.getViewById("searchBar");
searchBar.on("propertyChange", function (args) {
var searchText = args.object.text;
if (searchText === "") {
} else {
while (resultList.length > 0) {
resultList.pop();
}
peopleList.forEach(function (element) {
if (element.fullName === searchText) {
resultList.push(element);
}
});
}
});
};
I had the same issue. If you want to filter your data after every character has changed in search-bar you can try my solution.
Definitions
My playerList is your peopleList. This is the data from view-model.
resultList is an array where the data will be pushed.
var observableArrayModule = require("data/observable-array").ObservableArray;
var playerList = new PlayerListViewModel([]);
var resultList = new observableArrayModule([]);
var pageData = new observableModule.Observable({
resultList: resultList,
player: ""
});
Inside expors.loaded()
page = args.object;
searchBar = page.getViewById("search-bar");
page.bindingContext = pageData;
Load Initial Data - inside expors.loaded()
We are loading initial data when user navigates to the screen for the first time. We are also pushing the same data to resultList since we are using {{resultList}} in xml. You can add loadingIndicator while the list is populated.
playerList
.load()
.then(function() {
setTimeout(function() {
playerList.forEach(function (element) {
pageData.resultList.push(element);
});
}, 1000);
})
.catch(function(error) {
dialogsModule.alert({
message: "An error occurred while loading players.",
okButtonText: "OK"
});
});
Clear autofocus - inside expors.loaded()
This is to prevent keyboard from opening on initial screen navigation.
if (searchBar.ios) {
searchBar.ios.endEditing(true);
} else if (searchBar.android) {
searchBar.android.clearFocus();
}
Search data when character has changed - inside expors.loaded()
I am calling filter functionality. Lodash _.debounce function is used to delay looping through resultList array. Without it, the app would loop every time letter is typed. Now we are waiting for user to stop typing to start looping.
searchBar.on('propertyChange', _.debounce(searchList, 500));
searchList Function
This is the actual loop. You can change element.name for your needs.
function searchList(args) {
var searchText = args.object.text;
while(resultList.length > 0) {
resultList.pop();
}
playerList.forEach(function (element) {
if (element.name.toLowerCase().indexOf(searchText) >= 0) {
resultList.push(element);
}
});
}
Hide keyboard if search-bar is cleared - inside exports.loaded()
And finally we want to hide the keyboard if user clears the search-bar.
searchBar.on(searchBarModule.SearchBar.clearEvent, function (args) {
setTimeout(function() {
searchBar.dismissSoftInput();
}, 10);
});
PS
You probably solved your issue, but this could help someone else in the future.
Okay so your problem is a Javascript problem than a NativeScript problem. For the sake of this problem, think of observable arrays as just your ordinary arrays.
In your JS you're creating a new PeopleListViewModel which you're then attaching to the bindingContext via the pageData object. So far so good. Then you're calling the load method on the PeopleListViewModel (It returns a promise which you're not really doing anything with but for this specific problem it doesn't matter).
However, when text is inputed you're not really doing anything. This is your code:
peopleList.filter(function (element, index, array) {
// DOESN"T WORK PROPERLY
console.log("element: ", JSON.stringify(element));
return element.fullName == searchText;
});
peopleList is an instance of PeopleListViewModel which returns an ObservableArray. The ObservableArray does indeed have a method called filter (which works just like filter of a regular array. Check out the NativeScript documentation and Javascript documentation of filter).
What you need to understand here is that filter returns a new array with the filtered results. Doing peopleList.filter() will send that new array into empty space. You want to var yourNewFilteredArray = peopleList.filter(). But you don't really want to redefine the array bound to the binding context, you want to modify the content of it.
Here's an example of how you could do that:
/*
* Attach a new obsersable array to the binding context.
* you can prepopulate it with the data from the
* PeopleListViewModel if you want to
*/
var resultList = new ObservableArray([]);
var pageData = new Observable({ resultList: resultList });
/*
* Then on search/filter you want to modify this new
* array. Here I first remove every item in it and then
* push matching items to it.
*/
searchBar.on("propertyChange", function (args) {
var searchText = args.object.text;
// ...
while(resultList.length > 0) {
resultList.pop();
}
peopleList.forEach(function (element) {
if (element.fullName === searchText) {
resultList.push(element);
}
});
});

knockout viewmodel and requirejs

I have recently started to work with requirejs and when I try to create a simple viewmodel I get an strange exception. The exception comes from the knockout-2.1.0.js file and the exception is "Only subscribable things can act as dependencies".
define("PageViewModel", ["knockout-2.1.0"], function(ko) {
return function PageViewModel() {
var self = this;
self.visiblePage = ko.observable("StartPage");
self.showPage = function (pageName) {
self.visiblePage(pageName);
};
};
});
As you can see the viewmodel is extremly simple and since the error is in the knockout js file, it seems like requirejs is working as it should. I have been looking at: http://knockoutjs.com/documentation/amd-loading.html
The exception occur when coming to the line: self.visiblePage = ko.observable("StartPage");
Any ideas on what I'm doing wrong?
Thanks,
Ludwig
Update:
This is the module containing the pageviewmodel:
define("ViewModelFactory", ["StorageService", "PageViewModel", "AddUnitViewModel", "AddRoomViewModel"],
function (StorageService, PageViewModel, AddUnitViewModel, AddRoomViewModel) {
//var repositoryStorage = new StorageService();
var createAddRoomVM = function () {
var vm = new AddRoomViewModel();
vm.setRepository = StorageService.getRoomRepository();
return vm;
};
var createAddUnitVM = function () {
var vm = new AddUnitViewModel();
vm.setRepository = StorageService.getUnitRepository();
return vm;
};
var createPageVM = function () {
var vm = new PageViewModel();
return vm;
};
return {
createPageVM:createPageVM,
createAddRoomVM: createAddRoomVM,
createAddUnitVM: createAddUnitVM
};
});
And the module calling the factory
define("ApplicationViewModel", ["ViewModelFactory"],
function (viewModelFactory) {
mainVM = null;
var initVM = function () {
mainVM = {
page: viewModelFactory.createPageVM(),
addRoom: viewModelFactory.createAddRoomVM(),
addUnit: viewModelFactory.createAddUnitVM()
};
};
var getVM = function (viewName) {
switch (viewName) {
case "AddRoom":
return mainVM.addRoom;
case "AddUnit":
return mainVM.addUnit;
default:
return null;
}
};
var getPageVM = function () {
return mainVM.page;
};
return {
initVM: initVM,
getVM: getVM,
getPageVM: getPageVM,
mainVM: mainVM
};
});
And the class containing the applicationViewModel:
define("Bootstrapper", ["knockout-2.1.0", "Routing", "ApplicationViewModel"],
function (ko, routing, applicationViewModel) {
var run = function () {
applicationViewModel.initVM(); <-- after here mainVM.page is null
var mainVM = applicationViewModel.mainVM;
routing.initRouting(applicationViewModel);
ko.applyBindings(mainVM);
routing.showView("StartPage");
alert("Start");
};
return {
run: run
};
})
Your problem may have been caused by Knockout 2.1, which didn't work well when ko was not a global variable.
Knockout 2.2 should work fine, and I see from your comment this did indeed fix the problem.