I use in my application KockoutJS External Template Engine to be able to load external templates files(so i can use them again in many pages). It's works good and i could use templates in another folder and data displayed correctly
My problem is that i want to call some function after my template is fully rendered and i used this solution with custom bindings (thanks to #RP Niemeyer).
The problem is that custom binding is executed before the template html is fully rendered when using external template file.
But using template which is existed inside my html page the custom binding is executed after the template html is fully rendered.
My template:
<script type="text/html" id="report-template">
<li>
<ul data-bind="template: { name: 'report-template', foreach: childItems }">
</ul>
</li>
</script>
and that's how i call my custom binding jsTree
<div id="reports-tree">
<ul data-bind="template: { name: 'report-template', foreach: $root.ReportsViewModel.Reports }, jsTree: $root.ReportsViewModel.Reports"></ul>
</div>
and that's my custom binding code:
ko.bindingHandlers.jsTree = {
update: function (element, valueAccessor) {
var reports = ko.utils.unwrapObservable(valueAccessor());
if (reports.length > 0) {
$(element).parent().jstree({
"themes": {
"theme": "default",
"dots": false,
"icons": true,
"utl": "/jstree-style.css"
},
"plugins": ["themes", "html_data"]
});
}
}
}
This answer suggests that you can use setTimeout to your advantage: It moves your custom binding to the end of the execution queue.
ko.bindingHandlers.jsTree = {
update: function (element, valueAccessor) {
setTimeout(function () {
var reports = ko.utils.unwrapObservable(valueAccessor());
if (reports.length > 0) {
$(element).parent().jstree({
"themes": {
"theme": "default",
"dots": false,
"icons": true,
"utl": "/jstree-style.css"
},
"plugins": ["themes", "html_data"]
});
}
}, 0);
}
};
I'm not sure how well this works with a recursive binding, though.
Related
I've been trying to work this out but sofar have been unable to find an answer. My Polymer element loads a base template JSON file, which is then run through a dom-repeat to create a basic HTML page.
Then another JSON text-file is loaded, which completes the various areas of the HTML with a JS function.
Upon button-click form child, a function is run that triggers the loading of another JSON file that adds additional info. This all works fine.
But when I go out of the page and back in it, it has remembered all my settings but does not display things correctly. It displays the translatedText well and the html code is there, but it does not complete the html code for the originalText.
It seems to want to load the last JSON file before the DOM is properly rendered. So I want it to refresh the whole DOM, but how do I do this?
My MWE:
<template>
<iron-ajax
auto
url="basetext.json"
handle-as="json"
last-response="{{baseText}}"></iron-ajax>
<div class="textcontent">
<template is="dom-repeat" items="{{baseText.lines}}" as="line">
<div class="lineblock">
<div class="line" id="line{{line.lineid}}" inner-h-t-m-l="{{line.linetext}}"></div>
<template is="dom-if" if="[[extraShowEnabled]]">
<div class="linepi" id='linepi{{line.lineid}}' inner-h-t-m-l="{{line.linetext}}"></div>
</template>
</div>
</template>
</div>
<template is="dom-if" if="[[extraLoadEnabled]]">
<iron-ajax
auto
url="originaltext.json"
handle-as="json"
last-response="{{originalText}}"></iron-ajax>
</template>
<iron-ajax
auto
url="translatedtext.json"
handle-as="json"
last-response="{{translatedText}}"></iron-ajax>
</template>
<script>
Polymer({
is: 'text-page',
properties: {
translatedText: Object,
originalText: Object,
extraShowEnabled: {
type: Boolean,
value: false
},
extraLoadEnabled: {
type: Boolean,
value: false
},
showViewer: {
type: String,
value: "none"
}
},
observers: [
'setView(showViewer)',
' _computeSegments(translatedText,".line")',
' _computeSegments(originalText,".linepi")'
],
ready: function() {
this.addEventListener('eventFromChild', this.changeView);
},
changeView: function(event) {
this.showViewer = event.detail.selectedView;
},
setView: function(showViewer) {
\\ first some code here to reset all css.
if (showViewer === "none") {
this.extraShowEnabled = false;
this.extraLoadEnabled = false;
}
if (showViewer === "sidebyside") {
this.extraShowEnabled = true;
this.extraLoadEnabled = true;
this._computeSegments(this.originalText,".linepi");
this._addSideBySideCode();
}
},
_computeSegments: function(inputText,linetype) {
if (inputText) {
Array.from(this.querySelectorAll(linetype+" sc-segment")).forEach(item => item.innerHTML = inputText.segments[item.id]);
}
},
_addSideBySideCode: function() {
\\ this function just adds some css.
},
});
</script>
I think You should try to use a compute function result as a dom-repeat item source, something like this:
<template is="dom-repeat" items="{{itmesByParamsCompute(baseText, originalText, translatedText, extraloadEnabled, ...)}}" as="line">
Add as many params as You need to recompute on. Then that compute function should return a valid source anytime at least one of the paras changes.
Also keep in mind, that if any of these params will become undefined that compute function might be ignored completely. Work around for this is making this opposite way - one property which is modified from manny observers, something like this:
properties: {
items_to_use: {
type: Array,
value: []
},
translatedText: {
type: Object,
observer: 'updateItemsToUse'
},
originalText: {
type: Object,
observer: 'updateItemsToUse'
}
},
updateItemsToUse: function (data) {
let updatedArray = this.someMixFixFunction(this.item_to_use, data);
this.set('items_to_use', updatedArray);
},
someMixFixFunction: function (old_array, data_to_apply) {
// do some merging or what ever You need here, for example
let updatedArray = old_array.concat(data_to_apply);
return updatedArray;
}
I've wrapped bootstrapTable (https://github.com/wenzhixin/bootstrap-table) into a directive, like this:
Vue.directive('bootstraptable', {
priority: 1000,
params: ['url', 'resource-name'],
bind: function () {
var _self = this;
$(this.el)
.bootstrapTable({
pagination: true,
pageSize: 15,
pageList: [],
sidePagination: 'server',
url: this.params.url,
queryParams: function (params) {
return params;
},
cookie: true,
cookieExpire: '24h',
cookieIdTable: this.params.resourceName + '-table',
locale: 'it-IT'
}).on('load-success.bs.table', function (e, data) {
$('[data-toggle="tooltip"]').tooltip();
_self.vm.$compile(_self.vm.$el);
});
},
update: function (value) {
$(this.el).val(value)
},
unbind: function () {
$(this.el).off().bootstrapTable('destroy')
}
});
The JSON returned from the server contains a button with a v-on directive so I have to recompile the injected HTML rows to have the button directives properly working.
Anyway, it seems that the following code isn't working:
_self.vm.$compile(_self.vm.$el);
Am I missing something obvious?
The $compile method needs to be called on the elements that have to be compiled, not on the vm root element.
I changed the line:
_self.vm.$compile(_self.vm.$el);
with:
_.each($('[recompile]'), function(el){
_self.vm.$compile(el);
});
and added the attribute "recompile" to all the HTML elements that need to be recompiled.
This seems to be working as expected, do not hesitate to answer if there is a more conventional way to do that.
I just started using head.js for loading JS files asynchronously with dependencies.
Looking at the API documentation and examples, I see that you can apply labels to JS files, then run some conditions when they are loaded, heres an example in their documentation:
// same as above, but pass files in as an Array
head.load([
{ label1: "file1.js" },
{ label2: "file2.js" }],
function() {
// do something
});
// Labels are usually used in conjuntion with: head.ready()
head.ready("label1", function() {
// do something
});
So basically, that will execute anything in the 2nd block of code (inside the closure), when the file1.js is loaded.
What I wanted to do, is load more files (asynchronously) when other files are loaded..
For example..
First, load the jquery file: jquery.min.ks
After thats successfully loaded, asynchronously load DataTables files: [ dataTables.searchHighlight.min.js, dataTables.conditionalPaging.js, jquery.dataTables.yadcf.js ]
After that, load the main application files: [ myapp_core.js, myapp_whatever.js ]
Heres the code I have now, using head.js, It seems to work ok, but it just doesn't seem right (Having to throw head.load() inside other functions)
// Step #1
head.load(
// First, load the main JS library
[
{ jquery_core: "/include/plugins/jquery-1.11.3/jquery-1.11.3.min.js" },
{ jquery_ui: "/include/plugins/jquery-ui-1.10.4/ui/minified/jquery-ui.min.js" }
],
function() {
console.debug('Loaded jQuery library files, loading jQuery plugin files..');
jquery_plugins();
}
);
// Step #2
function jquery_plugins() {
head.load([
// Load all the jQuery plugin files
"/include/plugins/bootstrap-3.2.0/js/bootstrap.min.js",
"/include/plugins/select2/select2.js",
"/include/plugins/bootstrap3-editable/js/bootstrap-editable.min.js",
"/include/plugins/bootstrap3-editable/inputs-ext/address/address.js",
"/include/plugins/bootstrap3-editable/inputs-ext/typeaheadjs/lib/typeahead.js",
"/include/plugins/bootstrap3-editable/inputs-ext/typeaheadjs/typeaheadjs.js",
"/include/plugins/bootstrap3-editable/inputs-ext/wysihtml5/wysihtml5.js",
"/include/plugins/mustache/mustache.js",
"/include/plugins/slimscroll/jquery.slimscroll.min.js",
"/include/plugins/jquery-cookie/jquery.cookie.js",
"/include/plugins/gritter/js/jquery.gritter.js",
"/include/plugins/jquery-tooltipster-master/js/jquery.tooltipster.js",
"/include/plugins/bootstrap-wizard/js/bwizard.js",
"/include/js/apps.js",
"/include/js/enhanced-select.jquery.min.js",
"/include/js/jquery.multi-select.js",
"/include/plugins/intro/intro.js",
"/include/plugins/switchery/switchery.min.js",
"/include/js/jquery.multiselect.js",
"/include/plugins/parsley/src/parsley.js",
"/include/plugins/parsley/src/extra/validator/APPcustom.js",
"/include/plugins/parsley/src/extra/plugin/parsley.remote.js",
"/include/plugins/bootstrap3-timepicker2/js/bootstrap-timepicker.js",
"/include/plugins/bootstrap-datetimepicker/js/bootstrap-datetimepicker.js",
"/include/plugins/SamWM/numeric/jquery.numeric.min.js",
"/include/plugins/moment/moment.min.js",
"/include/plugins/flot/jquery.flot.min.js",
"/include/plugins/flot/jquery.flot.time.min.js",
"/include/plugins/flot/jquery.flot.resize.min.js",
"/include/plugins/flot/jquery.flot.pie.min.js",
"/include/plugins/flot/jquery.flot.stack.min.js",
"/include/plugins/flot/jquery.flot.crosshair.min.js",
"/include/plugins/flot/jquery.flot.categories.js",
"/include/plugins/sparkline/jquery.sparkline.js",
"/include/plugins/bootstrap-tagsinput/bootstrap-tagsinput.min.js",
"/include/plugins/bootstrap-tagsinput/bootstrap-tagsinput-typeahead.js",
"/include/plugins/jquery-tag-it/js/tag-it.min.js",
"/include/plugins/bootstrap-select/bootstrap-select.min.js",
"/include/plugins/contextMenu/src/jquery.ui.position.js",
"/include/plugins/contextMenu/src/jquery.contextMenu.js",
"/include/js/jquery.mask.min.js",
"/include/plugins/jquery-jstree/dist/jstree.min.js",
"/include/plugins/jquery.expander.js",
"/include/plugins/jquery-labelauty/source/jquery-labelauty.js",
"/include/plugins/jquery-file-upload/js/vendor/jquery.ui.widget.js",
"/include/plugins/jquery-jscroll/jquery.jscroll.js",
"/include/js/modernizr.js",
"/include/plugins/jquery-messi/dist/messi.js",
"/include/plugins/jquery-tmpl/jquery.tmpl.min.js",
{ jquery_highlight: "/include/plugins/jquery-searchhighlight/jquery.highlight.js" },
{ datatables_core: "/include/plugins/DataTables/minify/datatables.min.js" },
{ datatables_searchHighlight: "/include/plugins/DataTables-Plugins/features/searchHighlight/dataTables.searchHighlight.min.js" },
{ datatables_conditionalPaging: "/include/plugins/DataTables-Plugins/features/conditionalPaging/dataTables.conditionalPaging.js" },
{ datatables_ellipsis: "/include/plugins/DataTables-Plugins/dataRender/ellipsis.js" },
{ datatables_yadcf: "/include/plugins/yadcf-0.8.8/jquery.dataTables.yadcf.js" },
{ ckeditor: "/include/plugins/ckeditor/ckeditor.js" }
],
function() {
console.log('All jQuery plugins loaded, loading APP js files...');
APP_js_files();
});
}
// Step #3
function APP_js_files(){
head.load([
// Load the application JS files
{ APP_plugins: "/include/js/APP_plugin.js" },
{ APP_intro: "/include/js/APP_intro.js" },
{ APP_app: "/include/js/APP_app.js" },
{ APP_confirmation: "/include/js/APP_confirmation.js" }
],
function() {
"use strict";
console.log('Successfully loaded all APP js files!');
$.ajaxSetup({
cache:false
});
App.init();
template.init();
account.init();
admin.init();
assets.init();
forms.init();
});
}
The above code is inside init.js, which is loaded via..
<script type="text/javascript" src="/include/js/head.min.js" data-headjs-load="/include/js/init.js"></script>
Is this the right way to do it? The documentation on head.js is pretty limited.
Thanks
Current implementation don't support query or ordering of loading, as I can sas in source code, that's why your solution of the problem is one of the best.
can any one give me some idea about difference between polymer,x-tag and vanilla js ?
I have used polymer in my project, but i want comparison of polymer,x-tag and vanilla js.
VanillaJS only means using web-components without any framework/wrapper in pure JS.
You have to register your custom-element, stamping out the element and taking care of data-binding yourself.
Both x-tag and Polymer provide a convenient and opinionated wrapper around web-components that greatly reduce boilerplate code.
IMHO the Polymer library provides the most declerative approach (regarding data-binding, defining templates, etc)
This is how it looks like with x-tag:
xtag.register('x-accordion', {
// extend existing elements
extends: 'div',
lifecycle:{
created: function(){
// fired once at the time a component
// is initially created or parsed
},
inserted: function(){
// fired each time a component
// is inserted into the DOM
},
removed: function(){
// fired each time an element
// is removed from DOM
},
attributeChanged: function(){
// fired when attributes are set
}
},
events: {
'click:delegate(x-toggler)': function(){
// activate a clicked toggler
}
},
accessors: {
'togglers': {
get: function(){
// return all toggler children
},
set: function(value){
// set the toggler children
}
}
},
methods: {
nextToggler: function(){
// activate the next toggler
},
previousToggler: function(){
// activate the previous toggler
}
}
});
This is how it would look like with Polymer:
<polymer-element name="polymer-accordion" extends="div" on-click="{{toggle}}">
<template>
<!-- shadow DOM here -->
</template>
<script>
Polymer('polymer-accordion' {
created: function() { ... },
ready: function() { ... },
attached: function () { ... },
domReady: function() { ... },
detached: function() { ... },
attributeChanged: function(attrName, oldVal, newVal) {
},
toggle : function() {....},
get togglers() {},
set togglers(value) {},
nextToggler : function() {},
previousToggler : function() {},
});
</script>
</polymer-element>
Web Components is the native implementation in the browsers.
Polymer is a library that act as a very thin layer on top of the Web Components technologies.
X-Tag is another library that is even thinner because it only relies on one of the four Web Components technologies.
I've written an article about that: http://pascalprecht.github.io/2014/07/21/polymer-vs-x-tag-here-is-the-difference/
I have a basic layout where different components can be selected using a tree view and then get rendered in the main panel. This works fine for all of my components (like grids) but glitches with forms.
The first time a form is selected it is fine, as soon as you try to select it again nothing gets rendered.
The demo is available here and there is a link to the javascript at the top of the page.
http://www.somethingorothersoft.com/ext
The selection of a component happens in selectNode function and I have tried everything i could without much result.
Edit as Jim Barrows pointed out it would be better to instantiate a component in the create function. I have been hesitant to do that as that is a fairly major change in the my real app and I wanted to actually keep the instances around for ease of navigation between them.
Now that I have written this I realised that to do state properly I would have to store it on the server regardless in case the browser navigates to another page...
Edit I made the change to always instantiate the forms like so, it's much more extJSy now :) :
components['Form1'] = { xtype:'form', "items": [
{ "name": "Rep_ID", "allowBlank": false, "fieldLabel": "Rep" },
{ "name": "Date", "allowBlank": false, "fieldLabel": "Date", "xtype": "datefield" },
{ "name": "Time", "allowBlank": true, "fieldLabel": "Time", "xtype": "timefield"}],
"defaults": { "xtype": "textfield" }
};
components['Form2'] = { xtype: 'form', "items": [
{ "name": "Date", "allowBlank": false, "fieldLabel": "Date", "xtype": "datefield" },
{ "name": "Time", "allowBlank": true, "fieldLabel": "Time", "xtype": "timefield"}],
"defaults": { "xtype": "textfield" }
}
Your problem is here:
var selectNode = function(node) {
node.select();
node = node.attributes;
var newpanel = components[node.component].create();
var cp = Ext.ComponentMgr.get('center_panel');
cp.setTitle(node.text, node.icon);
newpanel.hideMode = 'visibility';
newpanel.hide();
cp.removeAll();
cp.add(newpanel);
cp.doLayout();
newpanel.show();
};
and here:
create: function() { return this; }
The cp.removeAll() does in fact destroy all components. So when the create is called, there is no this to return, so nothing gets shown. The viewport component does automatically destroy anything removed, and the panel inherits this functionality. You can either set autoDestory to false, or do a new inside the create.