Knockout.js HTML template not responding to edits? - javascript

I don't even know how to describe this, it's so utterly confusing I have no clue where to begin.
I have a Knockout template stored in a <script type="text/html"> tag in my document. I want to edit this template. So I edit it and save it. I notice via the Web Development Tools that the change has taken place by inspecting the script tag and its value.
When Knockout.js applies the template to the DOM, the template has not been changed. In fact, I can entirely remove the template and Knockout magically places the old version of the template in there - as if it's caching it somehow.
I have tried Firefox and Google Chrome, both with cache resets.
For what it's worth, I am also using Require.js, however, I am unsure how this would affect anything as the templates I am trying to use with Knockout are embedded inline in the page, not loaded as an external file.
I suspect that either Knockout.js or Require.js is causing this, but I cannot figure out how.
Template in question (in create.blade.php):
<!-- Any edits or changes made to the following template will not be reflected when Knockout.js uses it, instead it appears to be 'caching' an old version of this template -->
<script type="text/html" id="image-file-template">
<div data-bind="attr: { 'data-index': $index }, visible: $root.visibleTemplate() == ko.unwrap($index)">
<h2 data-bind="text: original_name"></h2>
<form>
<ul class="container">
<li class="grid-4">
<label>
<p>Title</p>
<input type="text" name="title" data-bind="value: title" />
</label>
</li>
<!-- Snip -->
</ul>
</form>
</div>
</script>
Loading of my Knockout ViewModel in the DOM (create.blade.php)
<script data-main="/src/js/common" src="/src/js/require.js"></script>
<script>
require(['common'], function() {
require(['knockout', 'viewmodels/UploadViewModel'], function(ko, UploadViewModel) {
ko.applyBindings(new UploadViewModel());
});
});
</script>
How knockout selects my template on the ViewModel (UploadViewModel.js)
// Declare which template to use when a file is uploaded
self.templateObjectType = function (uploadedFile) {
var uploadedFileType = uploadedFile.type();
if (uploadedFileType == 1) {
return 'image-file-template'; // This is being called, I can confirm via the debugger, but the template being returned appears cached?
} else if (uploadedFileType == 2) {
return 'gif-file-template';
} else if (uploadedFileType == 3) {
return 'audio-file-template';
} else if (uploadedFileType == 4) {
return 'video-file-template';
} else if (uploadedFileType == 5) {
return 'document-file-template';
}
};
My Require.js config (common.js)
requirejs.config({
urlArgs: "bust=" + (new Date()).getTime(),
baseUrl: '/src/js',
paths: {
'text': 'lib/requirejs-text',
'jquery': 'lib/jquery-1.9.1',
'jquery.ui' : 'lib/jquery-ui.min',
"jquery.fracs": 'lib/jquery.fracs-0.15.0',
'dropzone': 'lib/dropzone',
"jquery.throttle-debounce": 'lib/jquery.ba-throttle-debounce.min',
'knockout': 'lib/knockout-3.2.0.debug',
'ko.mapping': 'lib/knockout.mapping-latest',
'ko.postbox': 'lib/knockout-postbox',
'd3': 'lib/d3',
'moment': 'lib/moment',
'sticky': 'lib/sticky'
},
shim: {
"jquery.fracs": ["jquery"],
"jquery.throttle-debounce": ["jquery"]
}
});
Has anyone ever encountered anything remotely like this?

Related

copying Javascript working on a laravel view to another site not working

So on similar sites with different themes, same core functions for laravel there is a view that has
<div class="footer__item footer__item--right"> <div class="footer__item-search"> <span class="search-wrap"><input type="text" placeholder="Search" class="search"></span> </div>
in scripts the only relative javascript code which is also already on the other site
$(document).on('keyup', '.search', function() {
var query = $(this).val().toLowerCase();
doSearch(query);
});
function OnSearch(input) {
var query = input.value.toLowerCase();
doSearch(query);
}
function doSearch(query){
$.getJSON('{{ route('frontend.game.search') }}?category1={{ $category1 }}&q=' + query, function(data) {
$('#games').html(data.data);
});
}```
so copying those makes a box appear but searches nothing
What possibly the javascript is missing to actually be called and call the laravel template view mentioned ?

Async loading a template in a Knockout component

I'm pretty experienced with Knockout but this is my first time using components so I'm really hoping I'm missing something obvious! I'll try and simplify my use case a little to explain my issue.
I have a HTML and JS file called Index. Index.html has the data-bind for the component and Index.js has the ko.components.register call.
Index.html
<div data-bind="component: { name: CurrentComponent }"></div>
Index.js
var vm = require("SectionViewModel");
var CurrentComponent = ko.observable("section");
ko.components.register("section", {
viewModel: vm.SectionViewModel,
template: "<h3>Loading...</h3>"
});
ko.applyBindings();
I then have another HTML and JS file - Section.html and SectionViewModel.js. As you can see above, SectionViewModel is what I specify as the view model for the component.
Section.html
<div>
<span data-bind="text: Section().Name"></span>
</div>
SectionViewModel.js
var SectionViewModel = (function() {
function SectionViewModel() {
this.Section = ko.observable();
$.get("http://apiurl").done(function (data) {
this.Section(new SectionModel(data.Model)); // my data used by the view model
ko.components.get("dashboard", function() {
component.template[0] = data.View; // my html from the api
});
});
}
return SectionViewModel;
});
exports.SectionViewModel = SectionViewModel;
As part of the constructor in SectionViewModel, I make a call to my API to get all the data needed to populate my view model. This API call also returns the HTML I need to use in my template (which is basically being read from Section.html).
Obviously this constructor isn't called until I've called applyBindings, so when I get into the success handler for my API call, the template on my component is already set to my default text.
What I need to know is, is it possible for me to update this template? I've tried the following in my success handler as shown above:
ko.components.get("section", function(component) {
component.template[0] = dataFromApi.Html;
});
This does indeed replace my default text with the html returned from my API (as seen in debug tools), but this update isn't reflected in the browser.
So, basically after all that, all I'm really asking is, is there a way to update the content of your components template after binding?
I know an option to solve the above you might think of is to require the template, but I've really simplified the above and in it's full implementation, I'm not able to do this, hence why the HTML is returned by the API.
Any help greatly appreciated! I do have a working solution currently, but I really don't like the way I've had to structure the JS code to get it working so a solution to the above would be the ideal.
Thanks.
You can use a template binding inside your componente.
The normal use of the template bindign is like this:
<div data-bind="template: { name: tmplName, data: tmplData }"></div>
You can make both tmplData and tmplName observables, so you can update the bound data, and change the template. The tmplName is the id of an element whose content will be used as template. If you use this syntax you need an element with the required id, so, in your succes handler you can use something like jQuery to create a new element with the appropriate id, and then update the tmplname, so that the template content gets updated.
*THIS WILL NOT WORK:
Another option is to use the template binding in a different way:
<div data-bind="template: { nodes: tmplNodes, data: tmplData }"></div>
In this case you can supply directly the nodes to the template. I.e. make a tmplNodes observable, which is initialized with your <h3>Loading...</h3> element. And then change it to hold the nodes received from the server.
because nodesdoesn't support observables:
nodes — directly pass an array of DOM nodes to use as a template. This should be a non-observable array and note that the elements will be removed from their current parent if they have one. This option is ignored if you have also passed a nonempty value for name.
So you need to use the first option: create a new element, add it to the document DOM with a known id, and use that id as the template name. DEMO:
// Simulate service that return HTML
var dynTemplNumber = 0;
var getHtml = function() {
var deferred = $.Deferred();
var html =
'<div class="c"> \
<h3>Dynamic template ' + dynTemplNumber++ + '</h3> \
Name: <span data-bind="text: name"/> \
</div>';
setTimeout(deferred.resolve, 2000, html);
return deferred.promise();
};
var Vm = function() {
self = this;
self.tmplIdx = 0;
self.tmplName = ko.observable('tmplA');
self.tmplData = ko.observable({ name: 'Helmut', surname: 'Kaufmann'});
self.tmplNames = ko.observableArray(['tmplA','tmplB']);
self.loading = ko.observable(false);
self.createNewTemplate = function() {
// simulate AJAX call to service
self.loading(true);
getHtml().then(function(html) {
var tmplName = 'tmpl' + tmplIdx++;
var $new = $('<div>');
$new.attr('id',tmplName);
$new.html(html);
$('#tmplContainer').append($new);
self.tmplNames.push(tmplName);
self.loading(false);
self.tmplName(tmplName);
});
};
return self;
};
ko.applyBindings(Vm(), byName);
div.container { border: solid 1px black; margin: 20px 0;}
div {padding: 5px; }
.a { background-color: #FEE;}
.b { background-color: #EFE;}
.c { background-color: #EEF;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="byName" class="container">
Select template by name:
<select data-bind="{options: tmplNames, value: tmplName}"></select>
<input type="button" value="Add template"
data-bind="click: createNewTemplate"/>
<span data-bind="visible: loading">Loading new template...</span>
<div data-bind="template: {name: tmplName, data: tmplData}"></div>
</div>
<div id="tmplContainer" style="display:none">
<div id="tmplA">
<div class="a">
<h3>Template A</h3>
<span data-bind="text: name"></span> <span data-bind="text: surname"></span>
</div>
</div>
<div id="tmplB">
<div class="b">
<h3>Template B</h3>
Name: <span data-bind="text: name"/>
</div>
</div>
</div>
component.template[0] = $(data)[0]
I know this is old, but I found it trying to do the same, and the approcah helped me come up with this in my case, the template seems to be an element, not just raw html

JavaScript function call on JQuery .load() doesn't behave like expected

I am working on a website for school, and am currently implement some sort of admin dashboard. For that, I decided to dynamically load 'modules' (actually simply .php files) into a div designed to hold them.
This works fine for modules that don't depend on specific js files, but there's one that needs the 'participation.js' file.
I had tested the module in a whole window where there was an 'onload="initSelectable()"' on the body directive, but calling this function when the module is loaded in the admin dashboard doesn't do anything.
Here is the content of participation.js (it is simply copy/pasted from the JQuery selectable, and I slightly modified the behaviour):
var selectedPlayerIDs = [];
function initSelectable(){
$('#selectable').selectable();
$('#submitParticipationBtn').hide();
console.log("initSelectable");
$("#selectable").selectable({
stop: function() {
var count = 8;
var result = $("#selectedPlayersCount").empty();
$(".ui-selected", this).each(function() {
count--;
selectedPlayerIDs.push($(this).attr("data-playerid"));
});
if(count > 1)
$('#selectedPlayersCount').html(count + " more players");
else if(count === 1)
$('#selectedPlayersCount').html(count + " more player");
else if(count === 0)
$('#selectedPlayersCount').html("no more player. You're good to go !");
else if(count === -1)
$('#selectedPlayersCount').html(-count + " player less");
else
$('#selectedPlayersCount').html(-count + " players less");
if(count === 0)
$('#submitParticipationBtn').show();
else
$('#submitParticipationBtn').hide();
}
});
}
function submitParticipation(){
alert( "JS loaded" );
$.post("participation.php", {selectedIDs : JSON.stringify(selectedPlayerIDs)}, function() {
})
.onSuccess(function() {
alert( "onSuccess" );
})
.fail(function() {
alert( "error" );
});
}
So basically this code initializes the JQuery Selectable environment. When loading the module in the div, I use $('#dynamicPage').hide().load("module1.php").fadeIn('500'); directly followed by $.getScript("participation.js");
The thing is, the module correctly loads (at least the HTML part), and I can see in the console log ("initSelectable"). But I need to manually re-execute initSelectable() from the command for it to be effective. And when I do that, I see there's an undefined getting logged in the console, before the second ("initSelectable") log (this might be due to the fact that I'm trying to call $('#selectable').selectable(); a second time).
For example, here is the participation module .php file:
<div class="well">
<h3>Create a participation</h3>
<h4>Please select <span id="selectedPlayersCount">8 players</span></h4>
<div class="row">
<div class="col-sm-4">
<ol id="selectable">
<?php include_once "../Ctrl/rankingList.php" ?>
</ol>
<button class="btn btn-success" id="submitParticipationBtn" onclick="submitParticipation()">Submit</button>
</div>
</div>
</div>
I've tried countless different way to call the initSelectable function (callbacks, events, timeOuts, etc...) and no matter what, even if it gets executed by the browser, I still need to manually re-execute it for it to be working...
So basically, my question is:
What is the correct way to load HTML and dependant JS files into a div ?
What is the correct way to load HTML and dependant JS files into a div ?
So, this would be a good start and you can take it from here.
$(function() {
$("#myDiv").load("myfile.php", function() {
console.log("HTML has been injected!");
//Get dependencies
$.getScript( "myscript.js" )
.done(function( script, textStatus ) {
//Call methods from within myscript.js
initSelectable();
})
.fail(function( jqxhr, settings, exception ) {
console.log("There was an error!");
});
});
// Remove inline event handler and bind it like below.
$("#myDiv").on("click", "#submitParticipationBtn", submitParticipation);
function submitParticipation() {
//...
//...
}
});
I am not sure why $('#selectable').selectable() is being duplicated. But, it's left you to fix :)
Okay so I was doing it wrong. I thought that putting the <script src "path/to/script.js"></script> in the module file didn't work. But actually, it does, and I simply needed to call $(document).ready(initSelectable()) in the JS file to be sure the initSelectable was executed at the right time.
So now my .php file looks like this:
<div class="well">
<h3>Create a participation</h3>
<h4>Please select <span id="selectedPlayersCount">8 players</span></h4>
<div class="row">
<div class="col-sm-4">
<ol id="selectable">
<?php include_once "../Ctrl/rankingList.php" ?>
</ol>
<button class="btn btn-success" id="submitParticipationBtn" onclick="submitParticipation()">Submit</button>
</div>
</div>
<script src="../Ctrl/participation.js"></script>
</div>
Thanks all for your help :P

IE9 ignores angular objects (within ngRepeat)

I've got an ng-include and ng-controller within an ng-repeat, and IE randomly craps itself when it sees the child instance object of the repeat:
inside main.html
<section ng-repeat="panel in sidepanels">
<h2 class="twelve columns">
<span class="twelve">
<i class="icon {{panel.icon}}"></i> <!-- resolves properly -->
{{panel.controller.name}} <!-- empty -->
</span>
</h2>
<div
ng-include src="'views/'+panel.controller.name.toLowerCase()+'.html'"
ng-controller="panel.controller"
></div>
</section>
inside controllers.js
function Main($scope) {
…
$scope.sidepanels = [
{
"controller": Alerts,
"icon": "icon-warning-sign"
}
];
…
}
function Alerts($scope,WebSocket) {
$scope.alerts = [];
WebSocket.on('…', function(data) { … });
WebSocket… //WebSocket is a Service
}
Except instead of throwing an error in console, it just silently ignores the fact that it sometimes can't resolve panel. I only noticed this was the case because I saw a failed GET on views/.html.
I checked MSDN, and supposedly IE supports the name property.
The property name doesn't work very well for functions on IE.
You can use the following snippet to retrieve a function's name (as described here):
func.toString().match(/^function ([^(\s]+)/)[1]
Add a function to your Main controller that creates the template path, like this:
$scope.getTemplatePath= function(controller) {
return 'views/' + angular.lowercase(controller.toString().match(/^function ([^(\s]+)/)[1] + '.html');
};
And on the HTML:
<div ng-include="getTemplatePath(panel.controller)" ng-controller="panel.controller"></div>
jsFiddle: http://jsfiddle.net/bmleite/faGRk/

How to bind click handlers to templates in knockoutjs without having a global viewModel?

I'm very new to KnockoutJs so I'm hoping that there is a well known best practice for this kind of situation that I just haven't been able to find.
I have a view model that contains an array of items. I want to display these items using a template. I also want each item to to be able to toggle between view and edit modes in place. I think what fits best with Knockout is to create the relevant function on either the main view model or (probably better) on each item in the array and then bind this function in the template. So I have created this code on my page:
<ul data-bind="template: {name: testTemplate, foreach: items}"></ul>
<script id="testTemplate" type="text/x-jquery-tmpl">
<li>
<img src="icon.png" data-bind="click: displayEditView" />
<span data-bind="text: GBPAmount"></span>
<input type="text" data-bind="value: GBPAmount" />
</li>
</script>
<script>
(function() {
var viewModel = new TestViewModel(myItems);
ko.applyBindings(viewModel);
})();
</script>
And this in a separate file:
function TestViewModel(itemsJson) {
this.items = ko.mapping.fromJS(itemsJson);
for(i = 0; i < this.items.length; ++i) {
this.items[i].displayEditView = function () {
alert("payment function called");
}
}
this.displayEditView = function () {
alert("viewmodel function called");
}
};
Due to the environment my JS is running in I can't add anything to the global namespace, hence the annonymous function to create and set up the view model. (There is a namespace that I can add things to if it is necessary.) This restriction seems to break all the examples I've found, which seem to rely on a global viewModel variable.
P.S. If there's an approach that fits better with knockoutJS than what I am trying to do please feel free to suggest it!
When your viewModel is not accessible globally, there are a couple of options.
First, you can pass any relevant methods using the templateOptions parameter to the template binding.
It would look like (also note that a static template name should be in quotes):
data-bind="template: {name: 'testTemplate', foreach: items, templateOptions: { vmMethod: methodFromMainViewModel } }"
Then, inside of the template vmMethod would be available as $item.vmMethod. If you are using templateOptions as the last parameter, then make sure that there is a space between your braces { { or jQuery templates tries to parse it as its own.
So, you can bind to it like:
<img src="icon.png" data-bind="click: $item.vmMethod" />
The other option is to put a method and a reference to anything relevant from the view model on each item. It looks like you were exploring that option.
Finally, in KO 1.3 (hopefully out in September and in beta soon) there will be a nice way to use something like jQuery's live/delegate functionality and connect it with your viewModel (like in this sample: http://jsfiddle.net/rniemeyer/5wAYY/)
Also, the "Avoiding anonymous functions in event bindings" section of this post might be helpful to you as well. If you are looking for a sample of editing in place using a dynamically chosen template, then this post might help.
This is for those asking how to pass variable methods (functions) to Knockout Template. One of the core features of Templating is the consuming of variable data, which can be String or function. In KO these variables can be embedded in data or foreach properties for the Template to render. Objects embedded in data or foreach, be it String, function etc, can be accessed at this context using $data.
You can look at this code and see if it can help you to pass functions to Knockout Template.
function ViewModel() {
this.employees = [
{ fullName: 'Franklin Obi', url: 'employee_Franklin_Obi', action: methodOne },
{ fullName: 'John Amadi', url: 'employee_John_Amadi', action: methodTwo }
],
this.methodOne = function(){ alert('I can see you'); },
this.methodTwo = function(){ alert('I can touch you'); }
}
ko.applyBindings(new ViewModel());
<ul data-bind="template: { name: employeeTemplate, foreach: employees }" ></ul>
<script type="text/html" id="employeeTemplate">
<li><a data-bind="attr: { href: '#/'+url }, text: fullName, click: $data.action"></a></li>
</script>
If you want to serve multiple Template constructs you can introduce a switch method to your ViewModel like this, and use as property to introduce alias for each item (employee). Make sure you add the switch key, linkable, to the item object.
...
this.employees = [
{ fullName: 'Franklin Obi', linkable : false },
{ fullName: 'John Amadi', url: 'employee_John_Amadi', action: methodTwo, linkable : true }
],
this.methodLinkTemplate = function(employee){return employee.linkable ? "link" : "noLink"; } //this is a two way switch, many way switch is applicable.
...
Then the id of the Template forms will be named thus;
<ul data-bind="template: { name: employeeTemplate, foreach: employees, as: 'employee' }" ></ul>
<script type="text/html" id="noLink">
<li data-bind="text: fullName"></li>
</script>
<script type="text/html" id="link">
<li><a data-bind="attr: { href: '#/'+url }, text: fullName, click: $data.action"></a></li>
</script>
I have not ran this codes but I believe the idea can save someones time.

Categories