Load external ViewModel and template for Knockout component - ajax

I'm trying to load the template to the viewmodel for a knockout component using require.js, but so far unsuccessful.
index.html (view)
<!doctype html>
<head>
<link href="assets/css/bootstrap.min.css" rel="stylesheet" />
<script data-main="assets/js/app" src="assets/js/vendor/require.js"></script>
</head>
<body>
<table_n></table_n>
</body>
menu.html (Template)
<table class="table table- bordered table-hover">
<thead>
<tr>
<th align=center width=60 style="display: none">Código</th>
<th>Título</th>
<th>Descrição</th>
<th>Ordem</th>
<th>Imagem url</th>
</tr>
</thead>
<tbody data-bind="foreach: {data: ListaUsuarios()}">
<tr>
<td style="display: none" data-bind="text: id"></td>
<td data-bind="text: titulo"></td>
<td data-bind="text: descricao"></td>
<td data-bind="text: ordem"></td>
<td data-bind="text: caminho_imagem"></td>
</tr>
</tbody>
</table>
menu.js (ViewModel)
define(['knockout', 'knockoutMapping'], function (ko, komap) {
debugger;
var self = this;
debugger;
self.filter = ko.observable('');
self.ListaUsuarios = ko.observableArray();
$.ajax({
type: "GET",
url: "http://192.168.15.3/api/menus",
contentType: "application/javascript",
dataType: "json",
success: function (result) {
var a = JSON.stringify(result);
var observableData = komap.fromJS(result);
var array = observableData();
self.ListaUsuarios(array);
}
});
});
App.js (initializing the knockout parameters)
(function (undefined) {
'use strict';
requirejs.config({
baseUrl: './', // Raiz
urlArgs: function (id, url) {
return (url.indexOf('?') === -1 ? '?' : '&') + 'v=23';
}, // Debug Cache
deps: ['assets/js/base'],
map: {
'*': {
'jQuery': 'jquery'
}
},
paths: {
// Módulos
'jquery': 'assets/js/vendor/jquery-3.1.1',
// Library jQuery
'knockout': 'assets/js/vendor/knockout-3.4.2',
'knockoutMapping': 'assets/js/vendor/knockout.mapping-latest',
// Config
'start': 'assets/js/start'
},
shim: {
'knockoutMapping': {
deps: ['knockout']
}
},
waitSeconds: 15
});
// Chamando módulo principal para iniciar a aplicação
require(['jquery'], function ($) {
require(['start']);
});
requirejs.onError = function (err) {
console.log(err.requireType);
console.log('modules: ' + err.requireModules);
throw err;
};
}());
base.js (Using jQuery as a module named in RequireJS)
(function () {
define(['jquery'], function () {
(function ($) {
console.info('Verificando Global jQuery...');
if (typeof window === 'object' && typeof window.document === 'object') {
if (!!window && !(!!window.$)) {
window.jQuery = window.$ = jQuery;
}
console.log([$, jQuery]);
}
var version = $().jquery;
if (typeof define === "function" && define.amd && define.amd.jQuery) {
console.info('jQuery: ' + version + ' $.fn.jquery: ' + $.fn.jquery);
return window.jQuery;
}
}(jQuery));
});
}());
start.js (And finally initializing the component)
define(['knockout', 'knockoutMapping'], function (ko, komap) {
debugger;
ko.components.register('table_n', {
viewModel: { require: 'assets/js/component/viewmodel/menu' },
template: { require: 'text!assets/js/component/templates/menu.html' }
});
ko.applyBindings();
});
And I get the many mistakes when i debug on browser
Verificando Global jQuery...
require.js:5 Array(2)
require.js:5 jQuery: 3.1.1 $.fn.jquery: 3.1.1
text.js Failed to load resource: the server responded with a status of 404 (Not Found)
app.js:36 scripterror
app.js:37 modules: text
app.js:38 Uncaught Error: Script error for "text", needed by: text!assets/js/component/templates/menu.html_unnormalized2
http://requirejs.org/docs/errors.html#scripterror
at makeError (require.js:5)
at HTMLScriptElement.onScriptError (require.js:5)
makeError # require.js:5
onScriptError # require.js:5
app.js:36 require
app.js:37 modules: null
app.js:38 Uncaught TypeError: Cannot read property 'createViewModel' of undefined
at c (knockout-3.4.2.js?v=23:80)
at Object.loadViewModel (knockout-3.4.2.js?v=23:82)
at Object.e [as Nb] (knockout-3.4.2.js?v=23:78)
at knockout-3.4.2.js?v=23:79
at Object.execCb (require.js:5)
at b.check (require.js:5)
at b.<anonymous> (require.js:5)
at require.js:5
at require.js:5
at each (require.js:5)
c # knockout-3.4.2.js?v=23:80
loadViewModel # knockout-3.4.2.js?v=23:82
e # knockout-3.4.2.js?v=23:78
(anonymous) # knockout-3.4.2.js?v=23:79
execCb # require.js:5
check # require.js:5
(anonymous) # require.js:5
(anonymous) # require.js:5
(anonymous) # require.js:5
each # require.js:5
emit # require.js:5
check # require.js:5
enable # require.js:5
init # require.js:5
h # require.js:5
completeLoad # require.js:5
onScriptLoad # require.js:5
app.js:36 timeout
app.js:37 modules: text!assets/js/component/templates/menu.html_unnormalized2
app.js:38 Uncaught Error: Load timeout for modules: text!assets/js/component/templates/menu.html_unnormalized2
http://requirejs.org/docs/errors.html#timeout
at makeError (require.js:5)
at l (require.js:5)
at require.js:5
makeError # require.js:5
l # require.js:5
(anonymous) # require.js:5
(unknown) XHR Loaded (menus - 200 OK - 971.9319999858271ms - 525B)

After seeing some examples about using require, I understood what I was doing wrong:
The index.html files, menu.html (template) are correct, but in the file menu.js (ViewModel) I changed the first line to include the Jquery library and looked like this:
Define (['jquery', 'knockout', 'knockoutMapping'], function ($, ko, komap) {
And at the end of the file after:
Self.User List (array);
I added the Knockout Applybinds:
Ko.applyBindings ();
Before it was in the start.js file, however as I'm querying a WebApi with ajax (which is asynchronous), it was activated before ajax finished querying, causing an error.
In the app.js file I added a lib called text.js in paths session, this libreary which can be found in download here
This library is required to load the template, because require by default loads .js files.
And also added the path to the viewmodel menu.js
The modified part of the file was thus
'text': 'assets/js/vendor/text',
'menu': 'assets/js/component/viewmodel/menu',
Remember that this is to be added in session paths after 'knockoutMapping' and before 'start'.
The base.js file also has not changed.
And in the file start.js was removed, as I said before, the applybindings and modified the first line. Now I called the Viewmodel and template by the variables that were referenced in the 'define' and the file looks like this:
Start.js:
define(['knockout', 'knockoutMapping', 'menu', 'text!assets/js/component/templates/menu.html'], function (ko, komap, menu, menuhtml) {
ko.components.register('table_n', {
viewModel: menu,
template: menuhtml
});
});
And with that the table was loaded correctly.

Related

How can we pass parameters to Alpine.data in Alpine.js v3?

I am now using the newest version of Alpine which is v3.
Making reusable components needs to be registered using the Alpine.data.
This is the alpinejs.js
import Alpine from 'alpinejs'
import form from './components/form'
window.Alpine = Alpine
Alpine.data('form', form)
Alpine.start()
This is what I have in the components/form.js
export default (config) => {
return {
open: false,
init() {
console.log(config)
},
get isOpen() { return this.open },
close() { this.open = false },
open() { this.open = true },
}
}
This is the html part:
<div x-data="form({test:'test'})"></div>
This is the error I get in the console:
Any idea how to pass parameters to Alpine.data?
I stumbled over this question, searching for an answer but figured it out now. Maybe its still usefull to someone...
You have do define the parameter when registering the data component:
document.addEventListener('alpine:init', () => {
window.Alpine.data('myThing', (param) => MyModule(param));
});
Now you can use it in your module on init...
export default (param) => ({
init() {
console.log(param);
}
});
... when you init the component
<div x-data="deliveryDate({ foo: 'bar' })"></div>
This likely happens since you imported your script as a module. Therefore, you need another script that handles initialization of data.
I'm using a vanillajs vite setup and here's a working implementation with alpinejs:
index.html
<head>
<!-- Notice the type="module" part -->
<script type="module" src="/main.js" defer></script>
<script src="/initializer.js"></script>
</head>
<body x-data="greetingState">
<button #click="changeText">
<span x-text="message"></span>
</button>
<h2 x-text="globalNumber"></h2>
</body>
main.js
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();
// const globalNumber = 10; // Wrong place
initialize.js
document.addEventListener('alpine:init', () => {
Alpine.data('greetingState', () => ({
message: "Hello World!",
changeText() {
this.message = "Hello AlpineJs!";
},
}));
});
const globalNumber = 10; // Correct place
Note that listening to the alpine:init custom event inside of a javascript module will break the app. The same happens if you try to display a variable from a script of type module, in this example globalNumber.

Render content with Vue syntax / component string through AJAX call?

I have this HTML pattern:
<div id="New"> == ajax loaded content == </div>
It was easy to render HTML at server side and use innerHTML to inject the content into the right place.
Now I am trying to use Vue.js to do the same thing but render HTML at the client side. I can make this pattern into a component, let's say componentA, with template:
componentA
template:
`<div><slot></slot></div>`
It works if the HTML page content is something like:
<componentA>
<componentB></componentB> and some other none component content
</componentA>
The componentB is rendered and replaced the slot in componentA.
The problem is how do I use AJAX call (the call is made outside of componentA) to load
<componentB></componentB> and some other none component content
into the slot of componentA, and still make componentB to render correctly?
In real situation, the content from AJAX call can be
<componentB>, <componentC>, <componentD> ...
The following will treat componentB as regular string
in HTML:
<componentA>
<div id="New"></div>
</componentA>
in JS:
document.getElementById('New').innerHTML =
'<componentB></componentB> And some other none component content';
Is there a proper way to render string from AJAX return with Vue syntax as Vue?
One solution is put the ajax response like <component></component> to Component.template inside render function (Vue Guide: Render Function).
Like below demo:
const Foo = Vue.component('foo', {template: '<p>Foo - {{flag}}</p>', props: ['flag']})
const Bar = Vue.component('bar', {template: '<p>Bar - {{flag}}</p>', props: ['flag']})
const Generic = Vue.component('generic', {
render: function (createElement) {
return createElement('div', [
createElement('h3', 'Title'),
createElement('button', {on: {click: this.loadComponent}}, 'Load Component'),
this.dynamicComponent
&& createElement(Vue.component('v-fake-slot', {template:this.dynamicComponent, props: ['flag']}), {
props: {
flag: this.parent
}
})
])
},
props: ['parent'],
data () {
return {
components: ['<foo :flag="flag"></foo>', '<bar :flag="flag"></bar>'],
index: 0,
dynamicComponent: ''
}
},
methods: {
loadComponent: function () {
setTimeout(() => {
this.index += 1
this.dynamicComponent = this.components[this.index % 2]
}, 1000)
}
}
})
new Vue({
el: '#app',
data () {
return {
test: 'root'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<generic :parent="test"></generic>
</div>

canjs component tempate dom live binding

My code is to realize a paginate page like this example, https://github.com/bitovi/canjs/blob/master/component/examples/paginate.html .
I found the {#messages}...{/messages} in message.mustache template was not been inserted into page , while messagelist component inserted event has been triggered, so i can not do any binding to {#messages} dom in the event, because it ‘not exists in the page.
Are there other ways to fix this problem?
The Templates:
message_list.mustache:
<app>
<messagelist deferredData='messagesDeferred'></messagelist>
<next-prev paginate='paginate'></next-prev>
<page-count page='paginate.page' count='paginate.pageCount'></page-count>
</app>
message.mustache:
{#messages}}
<dl>
<dt>.....</dt>
<dd>....</dd>
</dl>
{/messages}
The Component:
can.Component.extend({
tag: "messagelist",
template: can.view('/static/web/tpl/mobile/message.mustache'), // to load message template
scope: {
messages: [],
waiting: true,
},
events: {
init: function () {
this.update();
},
"{scope} deferreddata": "update",
update: function () {
var deferred = this.scope.attr('deferreddata'),
scope = this.scope,
el = this.element;
if (can.isDeferred(deferred)) {
this.scope.attr("waiting", true);
deferred.then(function (messages) {
scope.attr('messages').replace(messages);
});
} else {
scope.attr('messages').attr(deferred, true);
}
},
"{messages} change": function () {
this.scope.attr("waiting", false);
},
inserted: function(){
// can't operate the dom in message.mustache template
}
}
});
//to load component template
can.view("/static/web/tpl/mobile/message_list.mustache",{}, function(content){
$("#message-list").html(content)
});
I have solved the problem, but not the best, Maybe someone have a better way.
I changed my template, add a new component called <messageitem>
<messageitem> will load another template: message.mustache
Every <messageitem> will trigger inserted event when inserted into <messagelist>
The new component:
can.Component.extend({
tag: "messageitem",
template:can.view('/static/web/tpl/mobile/message.mustache'),
events: {
inserted: function(el, ev){
// Can-click can not satisfy my needs,
// because i call the third-party module to bind click event
// this module will be called repeatedly, not the best way
reloadModules(['accordion']);
}
}
});
// To load message_list.mustache
can.view("/static/web/tpl/mobile/message_list.mustache",{}, function(content){
$("#message-list").html(content)});
Static html:
<body>
<div id="message-list">
....
</div>
</body>
message_list.mustache:
<app>
<messagelist deferredData='messagesDeferred'>
{{#messages}}
<messageitem></messageitem>
{{/messages}}
</messagelist>
<next-prev paginate='paginate'></next-prev>
<page-count page='paginate.page' count='paginate.pageCount'></page-count>
</app>
message.mustache:
<dl class="am-accordion-item" >
...
</dl>

how to appear the image in ember with handlebars

Hello I want to appear the image from code i wrote below but i cant. Any ideas?
I googled and i found that i must use a helper function.
(template)
showallapps.hbs
{{#if getappsexist}}
{{#each app in getapp}}
{{#each app.app_files}}
{{#link-to "dashboard" app}}
<img {{bind-attr src=get_url}} class="img-responsive">
{{/link-to}}
{{/each}}
{{#link-to "dashboard" app}}
{{app.app_name}}
{{/link-to}}
(controller)
showallapps.js
import Ember from 'ember';
export default Ember.ObjectController.extend({
apps:[],
getappsexist: function () {
var appsexist = false;
if (this.store.all('userapp').content.length > 0) {
appsexist = true;
}
return appsexist;
}.property(),
getapp: function () {
this.apps = this.store.all('userapp');
return this.apps;
}.property(),
get_url: function (){
var url = 'http://' + JSON.parse(this.apps.content[2]._data.app_files).url;
return url;
}.property()
});
I have this json.
{
"userapp": [
{
},
{
"app_files": "{"url":"static.xxx.xxx/data/application/3911efd9-413a-11e1-b5e9-fbed80c8f6ba/eleutheris_epilogis.jpg","mime":"image/jpeg","name":"eleutheris epilogis.jpg"}"
}
]
}
I get these errors:
Uncaught Error: Assertion Failed: The value that #each loops over must be an Array. You passed {"url":"static.xxx.xxx/data/application/3911efd9-413a-11e1-b5e9-fbed80c8f6ba/eleutheris_epilogis.jpg","mime":"image/jpeg","name":"eleutheris epilogis.jpg"}
You need to form the image url as a property of some type in your controller (as you did with the getUrl computed property). Then you can bind to that by doing something like this:
<img {{bind-attr src=getUrl}} class="img-responsive" />

Having trouble updating a scope more than once

I'm using angular with the ionic framework beta 1.
Here's my ng-repeat html:
<a href="{{item.url}}" class="item item-avatar" ng-repeat="item in restocks | reverse" ng-if="!$first">
<img src="https://server/sup-images/mobile/{{item.id}}.jpg">
<h2>{{item.name}}</h2>
<p>{{item.colors}}</p>
</a>
</div>
And here's my controllers.js, which fetches the data for the ng-repeat from a XHR.
angular.module('restocks', ['ionic'])
.service('APIservice', function($http) {
var kAPI = {};
API.Restocks = function() {
return $http({
method: 'GET',
url: 'https://myurl/api/restocks.php'
});
}
return restockAPI;
})
.filter('reverse', function() {
//converts json to JS array and reverses it
return function(input) {
var out = [];
for(i in input){
out.push(input[i]);
}
return out.reverse();
}
})
.controller('itemController', function($scope, APIservice) {
$scope.restocks = [];
$scope.sortorder = 'time';
$scope.doRefresh = function() {
$('#refresh').removeClass('ion-refresh');
$('#refresh').addClass('ion-refreshing');
restockAPIservice.Restocks().success(function (response) {
//Dig into the responde to get the relevant data
$scope.restocks = response;
$('#refresh').removeClass('ion-refreshing');
$('#refresh').addClass('ion-refresh');
});
}
$scope.doRefresh();
});
The data loads fine but I wish to implement a refresh button in my app that reloads the external json and updates the ng-repeat. When I call $scope.doRefresh(); more than once, I get this error in my JS console:
TypeError: Cannot call method 'querySelectorAll' of undefined
at cancelChildAnimations (http://localhost:8000/js/ionic.bundle.js:29151:22)
at Object.leave (http://localhost:8000/js/ionic.bundle.js:28716:11)
at ngRepeatAction (http://localhost:8000/js/ionic.bundle.js:26873:24)
at Object.$watchCollectionAction [as fn] (http://localhost:8000/js/ionic.bundle.js:19197:11)
at Scope.$digest (http://localhost:8000/js/ionic.bundle.js:19300:29)
at Scope.$apply (http://localhost:8000/js/ionic.bundle.js:19553:24)
at done (http://localhost:8000/js/ionic.bundle.js:15311:45)
at completeRequest (http://localhost:8000/js/ionic.bundle.js:15512:7)
at XMLHttpRequest.xhr.onreadystatechange (http://localhost:8000/js/ionic.bundle.js:15455:11) ionic.bundle.js:16905
It looks like it's related to a bug, as per:
https://github.com/driftyco/ionic/issues/727
Which was referenced from:
http://forum.ionicframework.com/t/show-hide-ionic-tab-based-on-angular-variable-cause-error-in-background/1563/9
I'm guessing it's pretty much the same issue.
Maybe try instead using angular.element(document.getElementById('refresh')) for a possible workaround (guessing).

Resources