Durandal JS knockout Deferred updates - javascript

I am having an issue when enabling deferred updates in the knockout library. I have implemented Jquery datatables as a component, when navigating to a view that has this component i can see the following methods being called in order.
Getview>Activate>Attach
everything works as expected
But if i press f5 and refresh the page rather than navigating to it from another page it breaks and the following methods are called
Getview>Activate>Attach>Getview>Activate>Attach>Detach>Detach (not sure why its called twice in the end)
and it breaks, no table shows on the UI at all as it does not render from what i can tell, i think it has something to do with durandal transitions and there being a difference between navigating to a page and refreshing a page kinda grasping at straws tho.
This is a minimal class that replicates the problem for me, note i dont have an HTML file for this component i want to use the getView method to pass in some dynamic HTML from JQueryDT
I created a quick sample project with the bare minimum needed to replicate the problem.
https://bitbucket.org/dchosking1988/deferred-update-example
If you pull that and run it you will see that the "hello world" will disappear when you refresh the page but it wont if you navigate between tabs.
the general steps i used to replicate the issue are
1) download sample project
2) add test component (see repo above for the sample file)
3) enable deferred updates
4) disable view caching
4) try compose new instance of the component
Edits to give more info
*This is not a JQuery Datatable problem, it is replicated with the following
So you dont have to download the gitRepo, this is the code i can replicate the problem with in the sample project following the above steps.
define([],
function () {
var test = function () {
var self = this;
var defaultViewHtml = '<div> <h1>Hello World</h1></div>';
var currentView = null;
self.getView = function () {
console.log('GetView');
if (!currentView) {
currentView = $(defaultViewHtml)[0];
}
return currentView;
};
self.activate = function (activateOptions) {
console.log('Activate');
};
self.attached = function (view, parent, settings) {
console.log('Attatched');
};
self.detached = function (view, parent) {
console.log('Detatched');
};
};
return test;
});
Then Add this HTML to the index.html, also dont forget to create an instance of the class in the index.js
<div class="whiteRow">
<div class="container">
<div class="row">
<div class="col-md-12">
<div data-bind="compose: { model: test }"></div>
</div>
</div>
</div>
</div>

This is occurred because it call code twice and second called the currentView stay empty in test.js, I commented the stretch where you set the currentView and code work.
self.getView = function () {
console.log('GetView');
//if (!currentView) {
// currentView = $(defaultViewHtml)[0];
//}
return currentView;
};
-
<div class="whiteRow">
<div class="container">
<div class="row">
<div class="col-md-12">
<div data-bind="compose: { model: test }"></div>
</div>
</div>
</div>
</div>

define([],
function () {
var test = function () {
var self = this;
var defaultViewHtml = '<div> <h1>Hello World</h1></div>';
var currentView = null;
self.getView = function () {
console.log('GetView');
return currentView;
};
self.activate = function (activateOptions) {
console.log('Activate');
};
self.attached = function (view, parent, settings) {
console.log('Attatched');
};
self.detached = function (view, parent) {
console.log('Detatched');
};
};
return test;
});
currentView stay empty in test.js,

Related

Writing callbacks for a wizard?

I'm building a custom wizard in knockout that dynamically loads knockout components during each "step" of the wizard. I've managed to get all of this working without much hassle, and it seems to work pretty well.
However, I'm wanting to implement some callbacks within the wizard when certain events happen. For example, before and after navigation.
Currently, one of my navigation functions looks like this:
this.goNext = function () {
if (!this.canGoNext()) return;
this.currentStep(this.pages()[this.currentIndex() + 1]);
this.loadPage();
}
I would like to build 2 callback functions called beforePageChange and onPageChange.
My general assumption is that beforePageChange would pass in a couple parameters, notably the current page and the next page. However, I also want it to be able to be observed from any other class utilization the wizard.
For example, on my parent page I would have something like:
this.wizard = Site.loadWizard(arguments);
this.wizard.beforePageChange(function(options) {
if (!options.currentPage.complete) return false;
// do stuff
return true;
});
In turn the wizard would execute its navigation commands and trigger the appropriate callbacks.
I feel like there's something I'm just fundamentally missing here.
Edit
My current version works as follows:
In the wizard:
this.canChangePage = ko.obserable(true);
this.beforePageChange = function (options) {
};
this.beforePageChangeHandler = function (options) {
this.beforePageChange(options);
// do stuff
return true;
};
this.onPageChange = function (options) {
};
this.onPageChangeHandler = function (options) {
this.onPageChange(options);
//do stuff
return true;
}
On the page implementing the wizard:
this.wizard = Site.loadComponent(params, function () {
this.wizard.beforePageChange = function (options) {
this.canChangePage(false);
};
}.bind(this));
I'm not sure if there's a better way to implement this, or if this is the best solution.
The solution Tomalak described in their comment (I think):
Since you already have access to the wizard instance, you can subscribe to its currentStep observable. To get notifications before it changes, you pass a third parameter: "beforeChange". (The second is the this context).
var Wizard = function() {
this.currentStep = ko.observable(0);
}
Wizard.prototype.next = function() {
this.currentStep(this.currentStep() + 1);
}
Wizard.prototype.prev = function() {
this.currentStep(this.currentStep() - 1);
}
var Page = function() {
this.wizard = new Wizard();
this.wizard.currentStep.subscribe(function(oldStep) {
console.log("Page work before step change from step", oldStep);
}, this, "beforeChange");
this.wizard.currentStep.subscribe(function(newStep) {
console.log("Page work after step change to", newStep);
});
}
ko.applyBindings(new Page());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="with:wizard">
<button data-bind="click: prev">back</button>
<span data-bind="text: currentStep"></span>
<button data-bind="click: next">next</button>
</div>

Lazy load images, videos, and iframes in ReactJS rendered content

I have a react component rendering on page load. The content includes lots of rich media that I want to lazy load only when the content is on screen and subsequently unload it when it's not. More content is loaded as the user scrolls.
I'm using a combination of techniques to handle lazy loading iframes, videos, and images and it works well outside of content rendered via React. Mostly custom jQuery and the Lazy Load Anything library.
My main issue is that I can't get my lazy load function to trigger on content just placed into the dom. It works once the user resizes/scrolls (I have a events for this that are triggered appropriately). How do I get it to trigger when the content is available?
I've tried triggering it from componentDidMount but this doesn't seem to work as the content has yet to be placed into the DOM.
I suppose I could just check for content every n seconds but I'd like to avoid this for performance reasons.
Here's my simplified code:
var EntriesList = React.createClass({
render: function() {
var entries = this.props.items.map(function(entry) {
return (
<div className="entry list-group-item" key={entry.id}>
// lazy items video, image, iframe...
<img src="1px.gif" className="lazy" datasource="/path/to/original" />
<video poster="1px.gif" data-poster-orig="/path/to/original" preload="none">{entry.sources}</video>
</div>
);
});
return(<div>{entries}</div>);
}
});
var App = React.createClass({
componentDidMount: function() {
$.get('/path/to/json', function(data) {
this.setState({entryItems: data.entries});
}.bind(this));
// What do I put here to trigger lazy load? for the rendered content?
myLazyLoad(); // does not work on the new content.
},
getInitialState: function() {
return ({
entryItems: []
});
},
render: function() {
return (<div><EntriesList items={this.state.entryItems} /></div>);
}
});
React.render(<App />, document.getElementById('entries'));
With the npm package react-lazy-load-image-component, you just have to wrap the components that you want to lazy load with <LazyLoadComponent> and it will work without any other configuration.
import { LazyLoadComponent } from 'react-lazy-load-image-component';
var EntriesList = React.createClass({
render: function() {
var entries = this.props.items.map(function(entry) {
return (
<LazyLoadComponent>
<div className="entry list-group-item" key={entry.id}>
// lazy items video, image, iframe...
<img src="1px.gif" className="lazy" />
<video poster="1px.gif" data-poster-orig="/path/to/original" preload="none">{entry.sources}</video>
</div>
</LazyLoadComponent>
);
});
return(<div>{entries}</div>);
}
});
var App = React.createClass({
componentDidMount: function() {
$.get('/path/to/json', function(data) {
this.setState({entryItems: data.entries});
}.bind(this));
},
getInitialState: function() {
return ({
entryItems: []
});
},
render: function() {
return (<div><EntriesList items={this.state.entryItems} /></div>);
}
});
React.render(<App />, document.getElementById('entries'));
Disclaimer: I'm the author of the package.
If you are trying to use the jquery plugin you may end with a DOM out of sync with that rendered by React. Also in your case the lazy load function should be called in the EntriesList component, not from its parent.
You could use a very simple component as react-lazy-load:
https://github.com/loktar00/react-lazy-load
or just take inspiration from its source code to implement your own.
var EntriesList = React.createClass({
render: function() {
var entries = this.props.items.map(function(entry) {
return (
<div className="entry list-group-item" key={entry.id}>
// lazy items video, image, iframe...
<LazyLoad>
<img src="1px.gif" datasource="/path/to/original" />
<video poster="1px.gif" data-poster-orig="/path/to/original" preload="none">{entry.sources}</video>
</LazyLoad>
</div>
);
});
return(<div>{entries}</div>);
}
});
Try to check on scroll event to your div parent container (the div that you render on App class:
var App = React.createClass({
componentDidMount: function() {
$.get('/path/to/json', function(data) {
this.setState({entryItems: data.entries});
}.bind(this));
},
myLazyLoad: function(e) {
// here do actions that you need: load new content, do ajax request, ...
// example: check you scrolling and load new content from page 1, 2, 3 ... N
var self = this;
$.get('path/to/json/?page=N', function(data) {
self.setState({entryItems: data.entries});
});
},
getInitialState: function() {
return ({
entryItems: []
});
},
render: function() {
return (<div onScroll={this.myLazyLoad}><EntriesList items={this.state.entryItems} /></div>);
}
});
Lazy Loading React
A component can lazily load dependencies without its consumer knowing using higher order functions, or a consumer can lazily load its children without its children knowing using a component that takes a function and collection of modules, or some combination of both.
https://webpack.js.org/guides/lazy-load-react/

Knockout binding and CK Editor toolbar not appearing

I am totally new to knock-out custom binding, I am trying to integrate ckeditor with knock-out biding, I have the following binding got from Google search,
ko.bindingHandlers.wysiwyg = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
var allBindings = allBindingsAccessor();
var $element = $(element);
$element.attr('contenteditable', true);
if (ko.isObservable(value)) {
var isSubscriberChange = false;
var isEditorChange = true;
$element.html(value());
var isEditorChange = false;
$element.on('input, change, keyup, mouseup', function () {
if (!isSubscriberChange) {
isEditorChange = true;
value($element.html());
isEditorChange = false;
}
});
value.subscribe(function (newValue) {
if (!isEditorChange) {
isSubscriberChange = true;
$element.html(newValue);
isSubscriberChange = false;
}
});
}
}
}
I have the following code to bind,
$(function () {
$.getJSON("/getdata", function (data) {
ko.applyBindings({
testList: [{
test: ko.observable()
},
{
test: ko.observable()
}]
}, document.getElementById('htmled'));
});
});
HTML as follows
<div id="htmled" data-bind="foreach:testList">
Data
<div class="editor" data-bind="wysiwyg: test">Edit this data</div>
</div>
The binding works and show the toolbar when I call the ko.applyBindings outside the $.getJSON method. But when I call applyBindings inside, the toolbars not appearing. Can any body help me on this? I must be missing something for sure, any help on this is greatly appreciated.
Jsfiddle Added
Working :http://jsfiddle.net/jogejyothish/h4Lt3/1/
Not Working : http://jsfiddle.net/jogejyothish/Se8yR/2/
Jyothish
What's happening is this:
Your page loads with the single div. KO has yet to be applied to this div.
document.ready() fires. The CKEditor script applied CKEditor to any matching divs (none).
You make your ajax call.
The Ajax call completes. You apply bindings.
KO inserts two new divs, neither of which has CKEditor.
In order to fix it, you need to add some code inside your ajax success function to manually initialise the CKEditors, like:
$(".editor").each(function(idx, el) {
CKEDITOR.inline(el)
});
Here it is, working in your fiddle:
http://jsfiddle.net/Se8yR/5/
The reason your working version works is because the bindings are applied in document.ready, so KO renders the two div elements in time, and the CKEditor is successfully applied to them.
CKEditor takes some time to load.
In your first example, it loads after ko applies, which works fine.
In the second example, it loads before ko applies. The problem is that CKEditor looks for the contenteditable attribute which you set with ko, so the editor is not created.
You can create it manually with:
CKEDITOR.inline(element).setData(valueUnwrapped || $element.html());
Doc
Demo

Create a generic class to bind knockout object with pages

I am bit new to knockout and jquery mobile, There was a question which is already answered, I need to optimize the PageStateManager class to use generic bindings, currently PageStateManager can only use for one binding,I would really appreciate if someone can guide me to create a generic class to manage page states with knockout bindings Heere is the working code,http://jsfiddle.net/Hpyca/14/
PageStateManager = (function () {
var viewModel = {
selectedHospital: ko.observable()
};
var changePage = function (url, viewModel) {
console.log(">>>>>>>>" + viewModel.id());
$.mobile.changePage(url, {viewModel: viewModel});
};
var initPage = function(page, newViewModel) {
viewModel.selectedHospital(newViewModel);
};
var onPageChange = function (e, info) {
initPage(info.toPage, info.options.viewModel);
};
$(document).bind("pagechange", onPageChange);
ko.applyBindings(viewModel, document.getElementById('detailsView'));
return {
changePage: changePage,
initPage: initPage
};
})();
Html
<div data-role="page" data-theme="a" id="dashBoardPage" data-viewModel="dashBoardViewModel">
<button type="button" data-bind="click: goToList">DashBoard!</button>
</div>
New dashboard model
var dashBoardViewModel = function() {
var self = this;
self.userName = ko.observable('Welcome! ' + "UserName");
self.appOnline = ko.observable(true);
self.goToList = function(){
//I would like to use PageStateManager here
// PageStateManager.changePage($("#firstPage"),viewModel);
ko.applyBindings(viewModel,document.getElementById("firstPage"));//If I click Dashbord button multiple times it throws and multiple bind exception
$.mobile.changePage($("#firstPage"));
}
}
ko.applyBindings(dashBoardViewModel,document.getElementById("dashBoardPage"));
update url : http://jsfiddle.net/Hpyca/14/
Thank you in advance
I would probably go for creating a NavigationService which only handles changing the page and let knockout and my view models handle the state of the pages.
An simple example of such a NavigationService could be:
function NavigationService(){
var self = this;
self.navigateTo = function(pageId){
$.mobile.changePage($('#' + pageId));
};
}
You could then, in your view models just call it when you want it to navigate to a new page. One example would be upon selection of a hospital (which could be done either via a selection function or by manually subscribing to changes to the selectedHospital observable):
self.selectHospital = function(hospital){
self.selectedHospital(hospital);
navigationService.navigateTo('detailsView');
};
Other than the call to the navigationService to navigate, it's just ordinary knockout to keep track of which viewmodel should be bound where. A lot easier than having jquery mobile keeping track of which viewmodel goes where, if you ask me.
I have updated your jsfiddle to show a sample of how this could be done, making as few changes as possible to the HTML code. You can find the updated fiddle at http://jsfiddle.net/Hpyca/15/

Jasmine doesn't seem to be calling my method

I am new to Jasmine and seem to be struggling to get what I think is a fairy standard kind of thing running.
I am loading an HTML file via a fixture and trying to call a click on an element on the dom. This I would expect result in the call to the method of the JS file I am trying to test. When I try and debug this in developer tools the method that should be called in my js file never hits a breakpoint. As such I assume that code is not being called and therfore does not toggle the expand/collapse class.
My test:
describe("userExpand", function () {
beforeEach(function () {
loadFixtures('user-expand.html');
//userControl();
//this.addMatchers({
// toHaveClass: function (className) {
// return this.actual.hasClass(className);
// }
//});
});
//this test works ok
it("checks the click is firing", function () {
spyOnEvent($('.expanded'), 'click');
$('.expanded').trigger('click');
expect("click").toHaveBeenTriggeredOn($('.expanded'));
});
//this doesn't
it("checks the click is changing the class", function () {
//spyOnEvent($('.collapsed'), 'click');
var myElement = $('.collapsed');
myElement.click();
expect(myElement).toHaveClass('.expanded');
});
Part of the fixture:
<div class="wrapper">
<div class="row group">
<div class="col-md-1" data-bordercolour=""> </div>
<div class="collapsed col-md-1"> </div>
<div class="col-md-9">None (1)</div>
The JS I am trying to test:
var userControl = function () {
"use strict";
var collapse = '.collapsed';
var expand = '.expanded';
var userList = $(".userList");
function toggleState() {
var currentControl = $(this);
if (currentControl.hasClass('all')) {
if (currentControl.hasClass('expanded')) {
toggleIcon(currentControl, collapse);
userList.find(".user-group-summary").hide()
.end()
.find(".user-group-info").show();
} else {
toggleIcon(currentControl, expand);
userList.find(".user-group-summary").show()
.end()
.find(".user-group-info").hide();
}
} else {
currentControl.parent().nextUntil('.group').toggle();
currentControl.toggleClass("expanded collapsed");
currentControl.parent().find(".user-group-summary").toggle()
.end()
.find(".user-group-info").toggle();
}
};
function toggleIcon(ctrl, currentState) {
var details = ctrl.closest('div.row').siblings('.wrapper');
details.find(currentState).toggleClass('expanded collapsed');
if (currentState === expand) {
details.find('.detail').hide();
} else {
details.find('.detail').show();
}
}
userList.on('click', '.expanded, .collapsed', toggleState);
$('[data-bordercolour]').each(function () {
$(this).css("background-color", $(this).data('bordercolour'))
.parent().nextUntil('.group')
.find('>:first-child').css("background-color", $(this).data('bordercolour'));
});
return {
toggleState: toggleState
};
}();
The code works fine in normal use so I am sure I am missing something obvious with the way Jasmine should be used. Any help would be appreciated.
Update:
I can make the togglestate method fire by using call in the test rather than triggering a click event:
it('checks on click of icon toggles that icon', function () {
var myElement = $('.collapsed');
userControl.toggleState.call(myElement);
expect(myElement).toHaveClass('expanded');
});
This seems a little strange as all the examples I can find are quite happy with click. Gets me off the hook but I would still like to know what I am missing.
It's hard to give a precise hint without the source code. Does click on .collapsed involve asynchronous action(s)? If so, wrapping the test in runs(...); waitsFor(...); runs(...); may solve the problem. Check the Jasmine introduction for how to do this.

Categories

Resources