I am trying to figure out how to use a custom view location strategy, I have read the documentation at this page http://durandaljs.com/documentation/Using-Composition/ but I don't exactly understand what the strategy function should look like.
Can anybody give me a quick example of what the implementation of this function would be like and the promise that returns (even a simple one) etc?
Thanks in advance,
Gary
p.s. This is the code in my html:
<div>
<div data-bind="compose: {model: 'viewmodels/childRouter/first/simpleModel', strategy:
'viewmodels/childRouter/first/myCustomViewStrategy'}"></div> </div>
and this is the code in my myCustomViewStrategy:
define(function () {
var myCustomViewStrategy = function () {
var deferred = $.Deferred();
deferred.done(function () { console.log('done'); return 'simpleModelView'; });
deferred.fail(function () { console.log('error'); });
setTimeout(function () { deferred.resolve('done'); }, 5000);
return deferred.promise();
};
return myCustomViewStrategy;
});
but I get the error:
Uncaught TypeError: Cannot read property 'display' of undefined - this is after done has been logged in the console window.
Okay I solved this by creating my custom view strategy by the following:
define(['durandal/system', 'durandal/viewEngine'], function (system, viewEngine) {
var myCustomViewStrategy = function () {
return viewEngine.createView('views/childRouter/first/sModelView');
}
return myCustomViewStrategy;
});
As I found the documentation a bit lacking on compose binding's strategy setting I checked the source code how it works. To summ it up:
The module specified by the compose binding's strategy setting by its moduleId
must return a function named 'strategy'
which returns a promise which results in the view to be bound
as a HTML element object.
As a parameter the strategy method receives the compose binding's settings object
with the model object already resolved.
A working example:
define(['durandal/system', 'durandal/viewEngine'], function (system, viewEngine) {
var strategy = function(settings){
var viewid = null;
if(settings.model){
// replaces model's module id's last segment ('/viewmodel') with '/view'
viewid = settings.model.__moduleId__.replace(/\/[^\/]*$/, '/view');
}
return viewEngine.createView(viewid);
};
return strategy;
});
Durandal's source:
// composition.js:485
for (var attrName in settings) {
if (ko.utils.arrayIndexOf(bindableSettings, attrName) != -1) {
/*
* strategy is unwrapped
*/
settings[attrName] = ko.utils.unwrapObservable(settings[attrName]);
} else {
settings[attrName] = settings[attrName];
}
}
// composition.js:523
if (system.isString(context.strategy)) {
/*
* strategy is loaded
*/
system.acquire(context.strategy).then(function (strategy) {
context.strategy = strategy;
composition.executeStrategy(context);
}).fail(function(err){
system.error('Failed to load view strategy (' + context.strategy + '). Details: ' + err.message);
});
} else {
this.executeStrategy(context);
}
// composition.js:501
executeStrategy: function (context) {
/*
* strategy is executed
* expected to be a promise
* which returns the view to be bound and inserted to the DOM
*/
context.strategy(context).then(function (child) {
composition.bindAndShow(child, context);
});
}
Related
One little problem which JSONStore.add(data).then().fail()
The function initialiserBD() runs and returns success. The function remplireBD() doesn't return success. Surely, it is the function WL.JSONStore.get().add().then().fail()
Object "errorObject" send error :-50 PERSISTENT_STORE_NOT_OPEN
function wlCommonInit() {
initialiserBD();
remplireBD();
}
function initialiserBD() {
var collectionName="Personnes" ;
var collections = {};
collections[collectionName]= {};
collections[collectionName].searchFields={nom :'string'};
WL.JSONStore.init(collections).then(function(){})
.fail(function(errorObject) {
alert(errorObject.tostring());
});
}
function remplireBD(){
var data = {
nom :'Bill Gates'
};
var collectionName = 'Personnes';
WL.JSONStore.get(collectionName).add(data).then(function () {})
.fail(function (errorObject) {
alert(errorObject.toString());
});
}
I think your problem is two-fold...
You initialize the collection both before init and "after" init (var collectionName="Personnes" ;)
JavaScript is async, and you're calling initialiserBD and remplireBD one after the other instead of calling remplireBD in the success callback of initialiserBD, which could lead to trying to .get() before init() completed...
I am using Durandal 2.1, and I am having a problem with view composition. I have a view for managing many types of items. I also want a view to manage a subset of those types. So I created a manage view and a managesubset view. The managesubset view just composes the manage view and passes it an array containing the subset of items. This way the user can go to /100/manage or 100/managesubset where managesubset will only allow the user to manage a subset of items. I am using this pattern because I will have multiple different versions of managesubset.
My problem is that the canDeactivate method is not fired when going to managesubset. Is there anyway to fire the canDeactivate and Deactivate lifecycle events when composing?
According to #3 under Activator Lifecycle Callbacks here, I should be able to do this, but I cannot find any good examples.
Code:
manage.js
define(['durandal/app', 'plugins/router'], function (app, router) {
var constructor = function () {
var self = this;
//...variable creation and assignment
//life cycle events
self.activate = function (viewmodel) {
self.recordId(viewmodel.recordId);
self.assignableTypes(viewmodel.assignableTypes);
self.pageHeaderTitle = viewmodel.pageHeaderTitle;
self.pageHeaderIcon = viewmodel.pageHeaderIcon;
};
self.canActivate = function (id) {
var deferred = $.Deferred();
//check if user has access to manage equipment
};
self.canDeactivate = function () {
if (!self.saveSuccessfull() && this.isDirty()) {
return app.showMessage("You have unsaved changes, are you sure you want to leave?", "Unsaved Changes", ["Yes", "No"]);
}
else {
return true;
}
}
};
return constructor;
});
managesubset.js
define([], function () {
var recordId = ko.observable();
var manageRecord = ko.observable();
return {
recordId: recordId,
manageRecord: manageRecord,
activate: function (id) {
recordId(id);
manageRecord({
pageHeaderTitle: 'Manage Subset',
pageHeaderIcon: 'cb-subset',
assignableTypes: [102],
recordId: recordId()
});
},
canActivate: function (id) {
var deferred = $.Deferred();
//check if user has access to manage equipment
}
}
});
managesubset.html
<div data-bind="compose: { model: 'manage', activationData: manageRecord() }"></div>
The activate is called correctly each time. The deactivate and canDeactive are what don't work, and they are never called.
how can i get it work ?
define(['plugins/router', 'knockout', 'services/logger', 'durandal/app', 'mapping', 'services/routeconfig', 'services/dataBindingHandlers'], function (router, ko, logger, app, mapping, routeconfig, dataBindingHandlers) {
//#region Internal Methods
function activate() {
logger.log('Übersicht View Activated', null, 'newSearch', true);
return true;
}
//#endregion
//==jquery=================================================
function attached() {
}//-->end of attached()
var url = "https://www.googleapis.com/books/v1/volumes?q=the+Cat+In+The+Hat", path = $.getJSON(url);
path.then(function (data) {
console.log(data.items);
var viewModel = {
title: 'Overview',
query: ko.observable('')
};
viewModel.activate = activate();
viewModel.attached = attached();
viewModel.model = mapping.fromJS(data.items, {}, viewModel);
/*understanding ko.mapping.fromJS signature:knockoutjs.com/documentation/plugins-mapping.html
ko.mapping.fromJS(data) - this syntax will create view model.
ko.mapping.fromJS(data, mappingOptions) - this will create view model with particular options.
ko.mapping.fromJS(data, mappingOptions, viewModel) -
ko.mapping.fromJS(data, viewModel) -
ko.mapping.fromJS(data, {}, viewModel) - and this one convers your data without mapping options and put it to view model.
*/
viewModel.filteredItems = ko.computed(function () {
var search = this.query().toLowerCase();
alert("i'am here in viewModel.books computed");
console.log(viewModel.model);
return ko.utils.arrayFilter(this.model(), function (book) {
return book.id().toLowerCase().indexOf(search) >= 0 || book.kind().toLowerCase().indexOf(search) >= 0 || book.etag().toLowerCase().indexOf(search) >= 0
});
}, viewModel);
return viewModel;
});
});
Utility Functions in KnockoutJS
UPDATES: i recieve a loop of objects when i console.log(viewModel.model);
You haven't clearly stated what it is that doesn't work about it?
However, you probably need to add the activate and attached methods to the viewModel in order for them to be called by durandal.
viewModel.activate = activate;
viewModel.attached = attached;
You probably also intend this chunk of code to be called within the activate function and not in the define call:
var url = "https://www.googleapis.com/books/v1/volumes?q=the+Cat+In+The+Hat",path =$.getJSON(url);
path.then( function (data) {
var books = data.items;
console.log(books);
In my Knockout view model I have a Save() function which sends a jQuery POST request. Inside this POST request is a call to ko.toJS(this).
Whenever I call this Save function the browser becomes unresponsive and eventually tells me that there's too much recursion. Upon debugging (by using breakpoints), I found that when I call toJS() it appears to do some degree of cloning of the object, and in doing this cloning it calls the Save() function, which in turn calls toJS()... and there's the recursion.
Why exactly does this happen, and is there a way to avoid it without using toJSON()?
[I have another question regarding toJSON, and which explains why I don't want to use it.]
For the sake of completeness, here is my view model.
function vmDictionary(dict) {
if (dict === null || dict === undefined) {
return;
}
var self = this;
// directly-assigned variables
self.Concepts = new vmConcepts(dict.Concepts);
self.Deleted = ko.observable(dict.Deleted);
self.Description = ko.observable(dict.Description);
self.IncludeInSearch = ko.observable(true);
self.ID = ko.observable(dict.ID);
self.Languages = ko.observableArray(dict.Languages);
self.LastUpdate = new vmChangeRecord(dict.LastUpdate);
self.Name = ko.observable(dict.Name);
self.Public = ko.observable(dict.Public);
self.TemplateName = function(observable, bindingContext) {
return "dictionary-template";
};
// computed variables
self.PublicText = ko.computed(function() {
return sp.Utils.Localize(self.Public
? "Public"
: "Private");
});
// exposed functions
self.Save = function () {
$.ajax({
data: ko.toJSON(self),
dataType: "json",
type: "POST",
url: [...],
statusCode: {
200: function (response) {
console.log(response);
}
},
error: function (xmlHttpRequest, textStatus, errorThrown) {
console.log(xmlHttpRequest);
console.log(textStatus);
console.log(errorThrown);
}
});
};
}
UPDATE: added the entire view model (above).
You must be doing something wrong, works in a little fiddle for me
http://jsfiddle.net/brN9s/
ViewModel = function() {
this.someData = ko.observable("Test");
this.dto = ko.observable();
};
ViewModel.prototype = {
Save: function() {
this.dto(ko.toJS(this));
}
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
viewModel.Save();
I want to migrate the javascript in my site from YU2 to YUI3, but I am only a poor amateur programer and I am stuck at the first pitfall.
I have the following code:
MyApp.Core = function() {
return {
init: function(e, MyAppConfig) {
if (MyAppConfig.tabpanels) {
MyApp.Core.prepareTabpanels(MyAppConfig.tabpanels);
}
},
prepareTabpanels: function(tabpanels) {
// Code here
}
}
}();
var MyAppConfig = {
"tabpanels":{"ids":["navigation"]}
};
YAHOO.util.Event.addListener(window, "load", MyApp.Core.init, MyAppConfig);
How can I pass the MyAppConfig object to the MyApp.Core.init function by using YUI3 "domready" event listener?
Thanks in advance!
You should be able to do something like:
var MyApp = {};
MyApp.Core = function(){ return {
init: function(MyAppConfig) {
console.log(MyAppConfig);
},
prepareTabpanels: function(tabpanels) {
// Code here
}
}
}();
var MyAppConfig = {
"tabpanels":{"ids":["navigation"]}
};
YUI().use('node', 'event', function(Y){
Y.on('domready', MyApp.Core.init, this, MyAppConfig);
});
Note that the event is not passed in as the first parameter, it is the config.
Y.on accepts parameters as <event_type>, <callback_function>, <context>, <params>..
any parameter after the third item is passed through to the callback function so MyAppConfig becomes the first parameter in your init.
EDIT
See the YUI3 API documentation here: http://developer.yahoo.com/yui/3/api/YUI.html#method_on