knockout search issue Property 'model' of object #<Object> is not a function - durandal

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);

Related

Odoo: open full model page from add line in a one2many field

I made a custom model and i have a one2many field where i click on "add new line" then "search more" to look for the item i want to add just like the picture below
What I want is to click on "add new line", and the whole products page appear without having to go throw the previous process
You can extend ListRenderer and override _onAddRecord to open a search more view using a condition in context.
The following code adds a new widget to open search more... link and select many records.
odoo.define('stack_overflow.open_list_view', function (require) {
"use strict";
var pyUtils = require('web.py_utils');
var dialogs = require('web.view_dialogs');
var core = require('web.core');
var _t = core._t;
var fieldRegistry = require('web.field_registry');
var ListRenderer = require('web.ListRenderer');
var AddManyFieldOne2ManyRenderer = ListRenderer.extend({
/**
* It will returns the first visible widget that is editable
*
* #private
* #returns {Class} Widget returns first widget
*/
_getFirstWidget: function () {
var record = this.state.data[this.currentRow];
var recordWidgets = this.allFieldWidgets[record.id];
var firstWidget = _.chain(recordWidgets).filter(function (widget) {
var isLast =
widget.$el.is(':visible') &&
(
widget.$('input').length > 0 || widget.tagName === 'input' ||
widget.$('textarea').length > 0 || widget.tagName === 'textarea'
) &&
!widget.$el.hasClass('o_readonly_modifier');
return isLast;
}).first().value();
return firstWidget;
},
add_rows: function (lines, field_name) {
var self = this;
if (lines.length > 0) {
self.trigger_up('add_record', {
context: [{ [field_name]: lines[0] }],
forceEditable: "bottom",
allowWarning: true,
onSuccess: function () {
self.unselectRow();
lines.shift(); // Remove the first element after adding a line
self.add_rows(lines, field_name);
}
});
}
},
_onAddRecord: function (ev) {
// we don't want the browser to navigate to a the # url
ev.preventDefault();
// we don't want the click to cause other effects, such as unselecting
// the row that we are creating, because it counts as a click on a tr
ev.stopPropagation();
var self = this;
// but we do want to unselect current row
this.unselectRow().then(function () {
var context = ev.currentTarget.dataset.context;
if (context && pyUtils.py_eval(context).open_list_view) {
// trigger add_record to get field name and model
// you do not need to trigger add_record if you pass the field name and model in context
self.trigger_up('add_record', {
context: [{}],
onSuccess: function () {
var widget = self._getFirstWidget();
var field_name = 'default_' + widget.name;
var model = widget.field.relation;
self.unselectRow();
self._rpc({
model: model,
method: 'search',
args: [[]],
}).then(
function (result) {
return new dialogs.SelectCreateDialog(self, _.extend({}, self.nodeOptions, {
res_model: model,
context: context,
title: _t("Search: add lines"),
initial_ids: result,
initial_view: 'search',
disable_multiple_selection: false,
no_create: !self.can_create,
on_selected: function (records) {
self.add_rows(records.map(product => product.id), field_name);
}
})).open();
});
}
});
} else {
self.trigger_up('add_record', { context: context && [context] });
}
});
},
});
var AddManyFieldOne2Many = fieldRegistry.map.section_and_note_one2many.extend({
/**
* We want to use our custom renderer for the list.
*
* #override
*/
_getRenderer: function () {
if (this.view.arch.tag === 'tree') {
return AddManyFieldOne2ManyRenderer;
}
return this._super.apply(this, arguments);
},
});
fieldRegistry.add('add_many_one2many', AddManyFieldOne2Many);
});
To use it define the widget attribute of the one2Many field to
<field name="field_name" widget="add_many_one2many"...
And add the following option to the tree view of the one2Many field
<create string="Open list view" context="{'open_list_view': True}"/>
Salam Ahmed
you can create a function that take you directly to product page and assigned to a button like in wizard's when you click to create a new view or get data from another view or page

Durandal Composition Binding with canDeactivate

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.

Is there any kind of wildcard operator in the Durandal observable plugin, to subscribe to a change in any property?

Is there any kind of wildcard operator in the Durandal observable plugin, as there is in (for example) JsObservable?
The Durandal observable documentation gives this example:
var observable = require('plugins/observable');
var viewModel:{
firstName:'',
lastName:''
};
observable(viewModel, 'firstName').subscribe(function(value){
console.log('First name changed.');
});
viewModel.firstName = 'Test';
What I'd like to do is use a wildcard to subscribe to any changed property on the target. Something like this:
observable(viewModel, '*').subscribe(function(property, value){
console.log(property + ' changed.');
});
I don't see anything in the API documentation, but wondered if there was anything undocumented, or if anyone has a workaround to implement this behaviour.
Unfortunately, there is no wildcard operator for this functionality.
But you can easily create wrapper module for this functionality.
Here is small example:
var observable = require('plugins/observable');
var wildcardObservable = function(obj, changeCallback){
for(var prop in obj){
observable(obj, prop).subscribe(changeCallback);
}
}
var changeCallback = function() {
console.log('property changed.');
}
Usage:
var viewModel:{
firstName:'',
lastName:''
};
wildcardObservable(viewModel, changeCallback);
With thanks to U10 for the start above, (and with reference to a few examples on the web) I came up with the following, which uses a closure to track all the necessary properties. It's a bit messy but it does what I need for now - hopefully it will be of use to someone.
var ChangeTracker = (function () {
function ChangeTracker() {
}
ChangeTracker.prototype._trackChange = function (prop, target) {
var type = typeof (target[prop]);
var value = target[prop];
_logger.log("_trackChange", { target: target, prop: prop, type: type, value: value }, "CT");
_obs(target, prop).subscribe(function (newValue) {
var obj = {
target: target,
prop: prop,
newValue: newValue,
oldValue: value
};
_logger.log(">>>>>>>>>>>>>>> CHANGE!", obj, "CT");
value = newValue;
});
};
ChangeTracker.prototype.TrackChanges = function (target) {
var _this = this;
for (var prop in target) {
if (target.hasOwnProperty(prop)) {
this._trackChange(prop, target);
}
var underlying = ko.utils.unwrapObservable(target[prop]);
if (underlying instanceof Array) {
ko.utils.arrayForEach(underlying, function (item) {
_this.TrackChanges(item);
});
} else if (typeof underlying === "object") {
this.TrackChanges(underlying);
}
}
}
};
return ChangeTracker;
})();

Durandal Custom View Location Strategy

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);
});
}

Knockoutjs - function inside viewmodel causing undesirable recursion

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();