Inside my layout page i wanna run a script for translate the text of some enumumerators.
My layout looks like this:
<html>
<head>
...
<script src="~/js/ViewModels/Helpers/Translation.js"></script>
<script src="~/js/ViewModels/Helpers/Enumerators.js"></script>
...
</head>
<body>
...
</body>
</html>
My translation script:
'use strict';
var jsonTranstation = null;
$(function () {
getjson();
});
const getjson = () => {
$.getJSON('/lib/translation/en-EN.json', function (data) {
jsonTranstation = data;
});
}
const Translation = (value, ViewModel) => {
let a = null;
if (jsonTranstation ) {
a = jsonTranstation[ViewModel][value];
return a;
}
return '--';
}
My Enumerators script:
'use strict'
const EnumToolBar = {
NEW: { text: Translation('New', 'EnumToolBar'), prefixIcon: 'e-add', id: 'NEW' }
}
My JSON file (en-EN.json):
{
"EnumToolBar": {
"New": "New Value"
}
}
Using EnumToolBar.NEW.text in HomePage returns '--' instead of 'New Value'.
There are any way to read first script and the respective json file before any other script?
Related
I would like reuse my html components that contains some javascript code, so for simplify I bring one simple example:
index.html:
<!DOCTYPE html>
<html>
<head></head>
<body>
<my-component></my-component>
<script src="index.js"></script>
</body>
</html>
my-component.html:
<template>
<div id="something"></div>
<script>
// It doesn't work, this here is "window"
document.getElementById("something").innerHTML = "Something"
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, {mode: "no-cors"})
.then(res => res.text())
.then(html => {
const parser = new DOMParser()
const document = parser.parseFromString(html, 'text/html')
const head = document.head
const template = head.querySelector('template')
return template
})
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({mode: 'open'})
shadow.appendChild(document.importNode(template.content, true))
}
}
return customElements.define(name, UnityComponent)
}
function loadComponent (name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template))
}
return {loadComponent}
}())
makeComponent.loadComponent("my-component", "my-component.html")
I can with this code, but it copy all variables of the script to window:
<template>
<div id="something"></div>
<style onload="templFunc.apply(this.getRootNode())"></style>
<script>
function templFunc() {
// It works
let text = "Something"
this.querySelector('#something').innerHTML = text
// but...
console.log(window.text) // => "Something"
}
</script>
</template>
It doesn't make a sense, if the script is inside the template at least should can access the elements inside the template, else the template is almost not util for the javascript, so, I can't understand the intention of use script inside the template or how to reuse the web components that use javascript, Is it wrong do this?
So, How to I access the components in the script inside a template without copy all script variables to window?
As you found out <script> inside a <template> runs in Global scope
If you use Angular, note Angular bluntly removes all <script> content from Templates.
One workaround is to add an HTML element that triggers code within Element scope.
<img src onerror="[CODE]"> is the most likely candidate:
This then can call a Global function, or run this.getRootNode().host immediatly.
<template id=scriptContainer>
<script>
console.log("script runs in Global scope!!");
function GlobalFunction(scope, marker) {
scope = scope.getRootNode().host || scope;
console.log('run', marker, 'scope:', scope);
scope.elementMethod && scope.elementMethod();
}
</script>
<img src onerror="(()=>{
this.onerror = null;// prevent endless loop if function generates an error
GlobalFunction(this,'fromIMGonerror');
})()">
</template>
<my-element id=ONE></my-element>
<my-element id=TWO></my-element>
<script>
console.log('START SCRIPT');
customElements.define('my-element',
class extends HTMLElement {
connectedCallback() {
console.log('connectedCallback', this.id);
this.attachShadow({ mode: 'open' })
.append(scriptContainer.content.cloneNode(true));
}
});
</script>
More detailed playground, including injecting SCRIPTs, at: https://jsfiddle.net/WebComponents/q0k8ts6b/
Here is the solution,
my-component:
<template>
<div id="something"></div>
<script>
makeComponent.getComponent("my-component", "something").innerHTML = "Something"
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, { mode: "no-cors" })
.then((res) => res.text())
.then((html) => {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const head = document.head;
const template = head.querySelector("template");
return template;
});
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
this.setAttribute("id", name);
shadow.appendChild(document.importNode(template.content, true));
}
}
return customElements.define(name, UnityComponent);
}
function getComponent(host, query) {
return document.getElementById(host).shadowRoot.querySelector(query);
}
function loadComponent(name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template));
}
return { getComponent, loadComponent };
})();
makeComponent.loadComponent("my-component", "my-component.html");
However I think that this is not the better way, maybe I need use the events here, and pass the shadow scope to a listener that is called in the script tag in the template, but I don't know how to pass the scope to the event yet.
Up:
With events:
my-component:
<template>
<div id="something"></div>
<script>
document.addEventListener("custom-event", (e) => {
console.log(e.detail.target.shadowRoot.getElementById("date-year"));
})
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, { mode: "no-cors" })
.then((res) => res.text())
.then((html) => {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const head = document.head;
const template = head.querySelector("template");
return template;
});
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
shadow.appendChild(document.importNode(template.content, true));
const event = new CustomEvent("custom-event", {'detail': {
target: this
}});
document.dispatchEvent(event);
}
}
return customElements.define(name, UnityComponent);
}
function loadComponent(name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template));
}
return { loadComponent };
})();
makeComponent.loadComponent("my-component", "my-component.html");
However, I prefer the first solution even. But if you need of nested components the first doesn't work, you need of the second.
I am working on creating an extension for azure devops, which creates a custom tab and displays the result.
I uploaded the file using "##vso[task.addattachment]".
Eg: console.log('##vso[task.addattachment type=TestReport;name=MyReport;]c:/user/index.html');
I am having problem in consuming that file and displaying it on new tab, I went through the sample code provided by MS - build_release_enhancer
but still unable to display the file.
js file::
import Controls = require("VSS/Controls");
import VSS_Service = require("VSS/Service");
import TFS_Build_Contracts = require("TFS/Build/Contracts");
import TFS_Build_Extension_Contracts = require("TFS/Build/ExtensionContracts");
import DT_Client = require("TFS/DistributedTask/TaskRestClient");
import { error } from "azure-pipelines-task-lib";
export class InfoTab extends Controls.BaseControl {
constructor() {
super();
}
public initialize(): void {
super.initialize();
// Get configuration that's shared between extension and the extension host
var sharedConfig: TFS_Build_Extension_Contracts.IBuildResultsViewExtensionConfig = VSS.getConfiguration();
var vsoContext = VSS.getWebContext();
if(sharedConfig) {
// register your extension with host through callback
sharedConfig.onBuildChanged((build: TFS_Build_Contracts.Build) => {
this._initBuildInfo(build);
var taskClient = DT_Client.getClient();
taskClient.getPlanAttachments(vsoContext.project.id, "build", build.orchestrationPlan.planId,"ATTACHMENT_TYPE_HERE").then((taskAttachments) => {
$.each(taskAttachments, (index, taskAttachment) => {
if (taskAttachment._links && taskAttachment._links.self && taskAttachment._links.self.href) {
var recId = taskAttachment.recordId;
var timelineId = taskAttachment.timelineId;
taskClient.getAttachmentContent(vsoContext.project.id, "build", build.orchestrationPlan.planId,timelineId,recId,"ATTACHMENT_TYPE_HERE",taskAttachment.name).then((attachementContent)=> {
function arrayBufferToString(buffer){
var arr = new Uint8Array(buffer);
var str = String.fromCharCode.apply(String, arr);
return str;
}
var data = arrayBufferToString(attachementContent);
});
}
});
});
});
}
}
private _initBuildInfo(build: TFS_Build_Contracts.Build) {
}
}
InfoTab.enhance(InfoTab, $(".build-info"), {});
// Notify the parent frame that the host has been loaded
VSS.notifyLoadSucceeded();
Html file:
<!DOCTYPE html>
<head>
<script src="../lib/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init( {
usePlatformScripts: true,
// moduleLoaderConfig: {
// paths: { "sample": "sample" }
// }
});
VSS.ready(function() {
require(["sample/tab2"], function () { });
});
</script>
<style>
.build-info {
padding: 10px;
}
</style>
</head>
<body>
<div class="build-info"> </div>
</body>
</html>
The issue is resolved, actually the issue was with my vss-extension.json file. I had to declare scope:
"scopes": [
"vso.build_execute"
]
I have several fields which need to be initialized with CKEditor, for this I have created an helper class that contains the initEditor method.
The method below should return the initialized editor but it doesn't:
window.CKEditorHelper = window.CKEditorHelper || {};
(function (exports) {
exports.initEditor = function (input, myEditor) {
ClassicEditor
.create(document.querySelector(input), {
language: {
ui: 'en'
content: 'en'
}
})
.then(editor => {
myEditor = editor;
});
};
})(window.CKEditorHelper);
this is called in the following way:
let editor = null;
CKEditorHelper.initEditor('#description', editor);
so when I click on a button:
$('#save').on('click', function(){
console.log(editor.getData());
});
I get:
Cannot read property 'getData' of null
what I did wrong?
There are some issues on your code
let editor = null;
the let keyword only define a variable within function scope, when you use editor on another scope (your click handle event), it could be undefined
Another line
myEditor = editor;
This just simple made the reference to your original editor object will gone
Here is my solution to fix it
Change the way you init an editor like bellow
window.editorInstance = {editor: null};
CKEditorHelper.initEditor('#description', editorInstance);
Change your CKEditorHelper to
window.CKEditorHelper = window.CKEditorHelper || {};
(function (exports) {
exports.initEditor = function (input, myEditorInstance) {
ClassicEditor
.create(document.querySelector(input), {
language: {
ui: 'en'
content: 'en'
}
})
.then(editor => {
myEditorInstance.editor = editor;
});
};
})(window.CKEditorHelper);
And when you want to use your editor
console.log(editorInstance.editor.getData());
You can give this in javascript
$(document).ready(function () {
CKEDITOR.replace('tmpcontent', { height: '100px' })
})
take the value by using following
$('#save').on('click', function(){
var textareaValue = CKEDITOR.instances.tmpcontent.getData();
});
<label class="control-label">Message</label>
<textarea name="tmpcontent" id="tmpcontent" class="form-control"></textarea>
//OR in latest version
var myEditor;
ClassicEditor
.create( document.querySelector( '#description' ) )
.then( editor => {
console.log( 'Editor was initialized', editor );
myEditor = editor;
} )
.catch( err => {
console.error( err.stack );
} );
and then get data using
myEditor.getData();
I am trying to create a custom data adapter according to an example here: http://select2.github.io/announcements-4.0.html#query-to-data-adapter.
How can I move the line that creates the select2 control outside the function with definition of DataAdapter (see the code below)?
<!DOCTYPE html>
<head>
<title></title>
<link href="select2.css" rel="stylesheet" />
<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.4.js"></script>
<script type="text/javascript" src="select2.full.js"></script>
<script type="text/javascript">
$.fn.select2.amd.require(
['select2/data/array', 'select2/utils'],
function (ArrayData, Utils) {
function CustomData ($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
var data = {results: []};
data.results.push({id: params.term, text: params.term});
data.results.push({id: 11, text: 'aa'});
data.results.push({id: 22, text: 'bb'});
callback(data);
};
// Works if uncommented, but this line needs to be elsewhere (in $(document).ready()).
//$("#my").select2({tags: true, dataAdapter: CustomData});
});
$(document).ready(function() {
// This line does not work here.
$("#my").select2({tags: true, dataAdapter: CustomData});
});
</script>
</head>
<body>
<select id="my"></select>
</body>
</html>
you define it via AMD-Pattern:
$.fn.select2.amd.define('select2/data/customAdapter',[
'select2/data/array',
'select2/utils'
],
function (ArrayAdapter, Utils) {
function CustomDataAdapter ($element, options) {
CustomDataAdapter.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomDataAdapter, ArrayAdapter);
CustomDataAdapter.prototype.current = function (callback) {
callback(...);
};
return CustomDataAdapter;
}
);
var customAdapter=$.fn.select2.amd.require('select2/data/customAdapter');
$("#my").select2({
tags: true,
dataAdapter: customAdapter
});
For anyone trying to extend select2, here is an example :
// Require the adapter you want to override
$.fn.select2.amd.require(["select2/data/select"], function (Select) {
let CustomDataAdapter = Select;
// Simple example, just override the function
CustomDataAdapter.prototype.current = function (callback) {
// Your own code
};
// Example modifying data then calling the original function (which we need to keep)
let originalSelect = CustomDataAdapter.prototype.select;
CustomDataAdapter.prototype.select = function (data) {
// Your own code
// Call the original function while keeping 'this' context
originalSelect.bind(this)(data);
};
// Finally, use the custom data adapter
$('#my-select').select2({
dataAdapter: CustomDataAdapter
});
});
Example of select2 to handle big array. I am fetching data from server using ajax. Handling searching and pagination locally with more than 20000 data json.
<select id="widget_project"></select>
<script>
$(function () {
allProjects;// having all project json data
pageSize = 50
jQuery.fn.select2.amd.require(["select2/data/array", "select2/utils"],
function (ArrayData, Utils) {
function CustomData($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
var results = [];
if(p_term !== "" && typeof params.term === "undefined"){
params.term = p_term;
console.log(params.term);
}
if (params.term && params.term !== '') {
p_term = params.term;
results = findItem(params.term);
} else {
results = allProjects;
}
if (!("page" in params)) {
params.page = 1;
}
var data = {};
data.results = results.slice((params.page - 1) * pageSize, params.page * pageSize);
data.pagination = {};
data.pagination.more = params.page * pageSize < results.length;
callback(data);
};
$(document).ready(function () {
$("#widget_project").select2({
minimumInputLength: 3,
placeholder:"Select a project",
ajax: {},
dataAdapter: CustomData
});
});
})
});
</script>
I am new to backbone.js and is having some problem with my app. I have a collection which relies on a json data source. I am able to console.log the json in my parse method. Is that enough to bind the collection to my view? I don't understand the use of fetch method.
My collection looks like as follows,
(function (collections,model) {
collections.Directory = Backbone.Collection.extend({
initialize : function(){
console.log('we are here');
},
model:model.item,
url:'collections/json/data.json',
parse:function(response){
console.log(response);
return response;
}
});
})(app.collections,app.models);
And my master view looks like this,
(function(views,collections){
views.masterView = Backbone.View.extend({
el : $("#contacts"),
initialize : function(){
console.log('view initialize inside render');
this.render();
this.$el.find("#filter").append(this.createSelect());
this.on("change:filterType", this.filterByType, this);
this.collection.on("reset", this.render, this);
this.collection.on("add", this.renderContact, this);
//console.log('we are here'+app.collections.CollectionItems.fetch());
console.log('view initialize');
},
render : function(){
this.$el.find("article").remove();
_.each(this.collection.models,function(item){
this.renderContact(item);
},this);
},
renderContact: function (item) {
views.contactView = new app.views.ContactView({
model: item
});
this.$el.append(contactView.render().el);
},
getTypes : function () {
return _.uniq(this.collection.pluck("Qno"));
},
createSelect : function () {
var select = $("<select/>", {
html: "<option value='all'>All</option>"
});
_.each(this.getTypes(), function (item) {
var option = $("<option/>", {
value: item.toLowerCase(),
text: item.toLowerCase()
}).appendTo(select);
});
return select;
},
events: {
"change #filter select": "setFilter",
"click #add": "addContact",
"click #showForm": "showForm"
},
setFilter : function(e){
this.filterType = e.currentTarget.value;
this.trigger("change:filterType");
},
filterByType: function () {
if (this.filterType === "all") {
this.collection.reset(contacts);
routerURL.navigate("filter/all");
} else {
this.collection.reset(contacts, { silent: true });
var filterType = this.filterType,
filtered = _.filter(this.collection.models, function (item) {
return item.get("type").toLowerCase() === filterType;
});
this.collection.reset(filtered);
routerURL.navigate("filter/"+filterType);
}
},
addContact : function(e){
e.preventDefault();
var contModel = {};
$("#addContact").children("input").each(function(i, el){
if($(el).val() !== "")
contModel[el.id] = $(el).val();
});
contacts.push(contModel);
if (_.indexOf(this.getTypes(), contModel.type) === -1) {
this.collection.add(new Contact(contModel));
this.$el.find("#filter").find("select").remove().end().append(this.createSelect());
} else {
this.collection.add(new Contact(contModel));
}
},
showForm : function(){
this.$el.find("#addContact").slideToggle();
}
});
})(app.views,app.collections);
my model is very simple and looks like this,
(function ( models ) {
models.Item = Backbone.Model.extend({
defaults :{Qno:'1',Desc:'hello'}
});
})( app.models );
ihave one js file instantiatin viewsand collections
(function () {
window.app = {};
app.collections = {};
app.models = {};
app.views = {};
app.mixins = {};
$(function(){
app.collections.CollectionItems = new app.collections.Directory();
//app.collections.CollectionItems.fetch();
//console.log(app.collections.CollectionItems.fetch());
app.collections.CollectionItems.fetch({
success: function (collection,response) {
console.log(response);
}
});
//console.log(app.collections.CollectionItems.toJSON());
console.log('coll started');
app.views.app = new app.views.masterView({collection: app.collections.CollectionItems});
console.log('view is jus about fine!!');
//app.views.pagination = new app.views.PaginatedView({collection:app.collections.paginatedItems});
});
var ContactsRouter = Backbone.Router.extend({
routes: {
"filter/:type": "urlFilter"
},
urlFilter: function (type) {
master.filterType = type;
master.trigger("change:filterType");
}
});
var routerURL = new ContactsRouter();
Backbone.history.start();
})();
my landing page looks like this with a template in it
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Backbone.js Web App</title>
<link rel="stylesheet" href="css/screen.css" />
</head>
<body>
<div id="contacts">
</div>
<script id="contactTemplate" type="text/template">
<h1><%= Qno %></h1>
</script>
<script src="js/jquery.js"></script>
<script src="js/underscore-min.js"></script>
<script src="js/backbone-min.js"></script>
<script src="app.js"></script>
<script src="collections/Directory.js"></script>
<script src="models/item.js"></script>
<script src="views/masterView.js"></script>
<script src="views/simpleView.js"></script>
<!--<script src="js/backbone.paginator.js"></script>-->
</body>
</html>
I just can't get my head around this. The view is not rendered with the collection data. Please help!
I think it's because the fetch method on your collection is executed asynchronously and has therefore not completed when you create your view (if you look at the console I would expect the log statement in the success callback to display after the log statements underneath). This means that your view render method is called before the collection is populated and the reset event (which you're binding to in your view) is never triggered.
Try updating the code which instantiates everything as follows:
$(function(){
app.collections.CollectionItems = new app.collections.Directory();
//app.collections.CollectionItems.fetch();
//console.log(app.collections.CollectionItems.fetch());
app.collections.CollectionItems.fetch({
success: function (collection,response) {
console.log(response);
app.views.app = new app.views.masterView({collection: app.collections.CollectionItems});
}
});
});