I would really appreciate any help on this problem that I have spent many hours trying to solve. I have a jquery object that I created from elements on a web page:
var get_article = $('title, p, img');
When I console.log this, I get a nice, clean list of the elements and their index values. What I would like to do is add each element to a json object along with their index value and contents. I have tried this:
article = new Array();
$.each(get_article, function(){
$('title').each(function() {
var title_items = {
node: $(this).index(''),
html_element: "title",
content: $(this).text()
};
article.push(JSON.stringify(title_items));
});
$('p').each(function() {
var p_items = {
node: $(this).index(''),
html_element: "p",
content: $(this).text()
};
article.push(JSON.stringify(p_items));
});
$('img').each(function() {
var img_items = {
node: $(this).index(''),
html_element: "img",
content: $(this).attr("src")
};
article.push(JSON.stringify(img_items));
});
});
When I console.log the json object (article), all of the data is correct except the node values in the json object (article) are all over the place and they don't show the same as the index value in the original jquery object (get_article). Also, the data begins to repeat itself, so I have more nodes than was in the original jquery object (get_article). Please help!
Thanks all for your help. I was able to solve the problem using this code. Looking at your responses made me see that I needed to try another way. This is what worked for me:
for (i = 0; i < get_article.length; i++) {
if ($(get_article[i]).is('title')) {
var title_items = {
node: i,
html_element: "title",
content: $(get_article[i]).text()
};
article.push(JSON.stringify(title_items));
}
if ($(get_article[i]).is('p')) {
var p_items = {
node: i,
html_element: "p",
content: $(get_article[i]).text()
};
article.push(JSON.stringify(p_items));
}
if ($(get_article[i]).is('img')) {
var img_items = {
node: i,
html_element: "img",
content: $(get_article[i]).attr("src")
};
article.push(JSON.stringify(img_items));
}
};
You're not actually using get_article anywhere in your code.
$.each(get_article, function(){
$('title').each(function() {
var title_items = {
// ...
};
article.push(JSON.stringify(title_items));
});
// ....
});
is exactly the same as
$('title').each(function() {
var title_items = {
// ...
};
article.push(JSON.stringify(title_items));
});
It's hard to say for sure since you haven't provided enough details, but perhaps what you want is something like:
$.each(get_article, function(){
$(this).find('title').each(function() {
var title_items = {
// ...
};
article.push(JSON.stringify(title_items));
});
// ....
});
That's still not going to have the exact same effect as simply logging to the console, though. The order of the elements will be different because in the code you're first getting all the titles, then all the paragraphs, and then all the images. When you console.log the jQuery output, it's not segregating the elements by their type but simply showing them in their DOM order.
For example, if the DOM has
title 1
paragraph 1
title 2
the console.log will show
title 1
paragraph 1
title 2
while the (fixed) code will show
title 1
title 2
paragraph 1
Related
I'm just beginning learning to code and this is my first post here so please forgive me if I violate any rules here....
So here is my question, it seems quite simple but I can just not figure out what is the problem. I have a JSON file like this:
{"imgj":
[
{
"id":"1",
"user_id":"1",
"tag":"siteplan",
"imageurl":"images/cb.jpg"
},
{
"id":"2",
"user_id":"2",
"tag":"floorplan",
"imageurl":"images/cbb.jpg"
},
{
"id":"3",
"user_id":"1",
"tag":"section",
"imageurl":"images/postit.png"
},
{
"id":"4",
"user_id":"2",
"tag":"siteplan",
"imageurl":"images/avatar_default.jpg"
}
]
}
and in my .js file, I'm trying to get data of img from the JSON file, but always failed.
p.preload=function(){
var url ='imglist.json';
imglist = p.loadJSON(url);
for(var i=0; i<4; i++) {
imgurl = imglist.imgj[i].imageurl;
img[i]=p.loadImage("imgurl");
}
};
The result of the console is shown as below:
console.log(typeof imglist == 'object'); //return true
console.log(imglist); //return {} imgj:(4)[{...},{...},{...},{...}]
console.log(imglist.imgj[1]); //return undefined
it seems that my JSON file is successfully read as an object, but the property of it cannot be read. It's really weird.
This should work however it's only fetch workaround. I don't know how to access this not own property made by loadJSON.
p.preload= function(){
var url ='imglist.json';
fetch(url)
.then(data=>data.json())
.then(imglist=>{
//imglist = p.loadJSON(url);
console.log(imglist.imgj[0]); // Object { id: "1", user_id: "1", tag: "siteplan", imageurl: "images/cb.jpg" }
for(let i = 0;i< imglist.imgj.length;i++) {
imgurl = imglist.imgj[i].imageurl;
img[i]=p.loadImage(imgurl);
}});
};
After a bunch of fiddling I believe loadJSON is actually asynchronous behind the scenes, so your for-loop continues to execute before the file is done loading.
You could store the imglist in a global/classwide variable and do the for-loop inside the setup or draw function instead.
var imglist;
var imgs;
p.preload = function() {
var url = 'imglist';
imglist = loadJSON(url);
}
p.setup = function() {
imgs = imglist.map(function(imglistitem) {
return loadImage(imglistitem.imageurl);
});
}
I am not quite sure how p5.js is wired behind the scenes, but the example from the reference uses the draw() function to pick apart the json:
https://p5js.org/reference/#/p5/loadJSON
The code is getting an HTML file and configObject and according to this config object need to modify this htmlFile content(the code is working)
This is the input:
please notice that that in the firstobj of the array (there is scenario which the next attribute is not provided which say to put the new script at the bottom) the next said put the script tag after script that have ID "ui-boot"
var extendedHtmlObject = [{
type: 'script',
action: 'new',
content: 'console.log(‘hello world’);',
next: "ui-boot"
}, {
type: 'script',
id: "ui-boot",
action: 'upd',
innerElem: [{
type: 'attr',
id: 'data--ui-comersion',
content: '1.17'
}, {
type: 'attr',
id: 'src',
content: '/test/test2/-co.js'
}]
}
This is the main function:
getExtend: function(htmlContent, extendedHtmlObject) {
var self = this;
if (extendedHtmlObject) {
extendedHtmlObject.forEach(function(configs) {
switch (configs.type) {
case 'script':
htmlContent = self._handleScriptElement(htmlContent, configs);
break;
case 'head':
break;
}
});
}
return htmlContent;
},
This method determines if I need to create a new script or update existing script attributes according to the input object
_handleScriptElement: function(htmlFilecontent, configEntry) {
var oExtendedHTML = htmlFilecontent;
switch (configEntry.action) {
case 'new':
oExtendedHTML = this._createNewScript(htmlFilecontent, configEntry);
break;
case 'upd':
var sParsedHtml = this._htmlParser(oExtendedHTML);
oExtendedHTML = this._updateScript(oExtendedHTML, configEntry, sParsedHtml);
oExtendedHTML = this._convertHtmlBack(oExtendedHTML);
break;
}
return oExtendedHTML;
},
This is the method for creating new script with two option
1. the first fork need to parse the html
2. the second for doesn't.
_createNewScript: function(htmlFilecontent, configEn) {
var sScriptContent = this._createScript(configEntry.content);
if (configEn.next != null) {
var sParsedHtml = this._htmlParser(htmlFilecon);
$(sScriptContent).insertAfter(sParsedHtml.find('#' + configEn.next));
htmlFilecontent = this._convertHtmlBack(sParsedHtml);
} else {
//when the script is at the end of file
var iHeadEndTagPos = htmlFilecon.search("(| )* )*head(|*>");
htmlFilecon = htmlFilecon.substr(0, iHeadEndTagPos) + sNewScript + htmlFilecon.substr(iHeadEndTagPos);
}
return htmlFilecon;
},
This code is redundant and not efficient(I'm fairly new to JS), could I maybe improve it with JS prototype?
I want to do the parse just once in the start and the parseBack at the end(of looping the input object) but the problem is that in the createNewScript the second fork doesn't need to use the parser...
The code inside module of requireJS
update
To make it more clear, The external API have two input and one output
HTML file content
config object which determine how to update the HTML, for example to create new script (as the first object in the array
extendedHtmlObject ) or update existing script content such as
attributes values)
the output should be the extended HTML with all the modification
**update 2 **
If I can provide additional data to make it more clear please let me know what.
Maybe have a look at some of the multitude of templating systems out there.
Alternatively, simplify your html creation. If you use innerHTML to create DOM nodes, have the HTML file content just be a long string containing the full markup with placeholders for all values/classnames/etc.
eg: <script src="{{src}" id="{{id}}"></script> and then just replace all the placeholders with the values. Then you can just update the innerHTML of the target with this string.
If you use DOM node creation, document.createElement(), create all those template nodes in advance (documentFragments are really handy here). Then have one function that will check the 'extendedHtmlObject' settings and select either the node already on the page in case of update, or select your premade nodeList. Once you have the html structure as nodes, hand it off to a different function that will update all the nodes inside the structure with the new values in the 'extendedHtmlObject'. You can then append the nodes to their target.
In both cases you can juist throw the back and forth html parsing and branching away.
pseudocode:
(function myModule() {
var templates = {},
createTemplates = function() {
// Create all templates you'll use and save them.
// If you create nodes, also include a mapping from each node to its respective value
// eg: children[0] = 'label', children[1] = 'value'
},
updateTemplate = function( template, values ) {
// INNERHTML: for each placeholder 'valueName' in string template, replace placeholder by values[valueName]
// NODE CREATION: use the node mapping in the template to select each node in the template and replace its current value with values[valueName]
return template;
},
getTemplate = function( type, reference ) {
return (type === 'new') ? templates[reference] : $('#' + reference);
},
update = function( config ) {
var html = getTemplate(config.action, (config.next || config.id)),
updated = updateTemplate(html, config);
if (config.next) $('#' + config.next).insertAfter(html);
else $('#' + config.id).innerHTML = html /* OR */ $('#' + config.id).appendChild(html);
},
init = function() {
createTemplates();
};
}());
TLDR: Create new or grab existing HTML first, then update either the string or the nodes, then determine where it has to go. Make everything as general as possible.
The html creation function should be able to create any html element/update any template, not just script nodes.
The update function should not care if the html its updating existed before or is newly generated.
The function that puts the html back into the website should not be inside the creation functions.
This way you'll probably at first end up with just one big switch statement that will call the same functions with different parameters, which can easily be replaced by adding another config setting to the 'extendedHtmlObject'.
UPDATE:
I've created an example of a usable structure. It uses the DOM methods (createElement, etc) to create a new document containing the updated input string. You can change it to better match your specific input/output as much as you want. It's more of an example of how you can create anything you want with just a few general functions and alot of config settings.
This doesn't use the templetes I spoke before of though, since that would take longer to make. Just have a look at handlebars.js or mustache.js if you want templating.
But it better matches the structure you had yourself, so I hope it's easier for you then to pick the parts you like.
var originalHTML = '<script id="ui-boot" src="path/to/script/scriptname.js"></script>',
imports = {},
// IMPORTANT NOTE:
// Using a string as the body of a script element might use eval(), which should be avoided at all costs.
// Better would be to replace the 'content' config setting by a 'src' attribute and ahve the content in a seperate file you can link to.
configAry = [
{ // script-new
type: 'script',
action: 'new',
content: 'console.log(‘hello world’);',
next: "ui-boot"
},
{ // script-upd
type: 'script',
id: "ui-boot",
action: 'upd',
innerElem: [
{
type: 'attr',
id: 'data--ui-comersion',
content: '1.17'
},
{
type: 'attr',
id: 'src',
content: '/test/test2/-co.js'
}
]
}
],
// Extra helpers to make the code smaller. Replace by JQuery functions if needed.
DOM = {
'create' : function create( name ) {
return document.createElement(name);
},
// Create a new document and use the string as the body of this new document.
// This can be replaced by other functions of document.implementation if the string contains more than just the content of the document body.
'createDoc' : function createDoc( str ) {
var doc = document.implementation.createHTMLDocument('myTitle');
doc.body.innerHTML = str;
return doc;
},
'insertAfter' : function insertAfter(node, target, content ) {
target = content.querySelector(target);
if (target.nextSibling) target.nextSibling.insertBefore(node);
else target.parentNode.appendChild(node);
return content;
},
'replace' : function replace( node, target, content ) {
target = content.querySelector(target);
target.parentNode.replaceChild(node, target);
return content;
},
'update' : function update( node, textContent, attributes ) {
if (textContent) node.textContent = textContent;
return (attributes) ?
Object.keys(attributes).reduce(function( node, attr ) {
node.setAttribute(attr, attributes[attr]);
return node;
}, node) :
node;
}
},
// The actual module
moduleHTMLExtender = (function( imports ) {
var createExtension = function createExtension( extension, content ) {
switch (extension.action) {
case 'new' :
return {
'method' : 'insertAfter',
'node' : DOM.update(DOM.create(extension.type), extension.content),
'target' : '#' + extension.next
};
break;
case 'upd' :
return {
'method' : 'replace',
'node' : DOM.update(content.querySelector('#' + extension.id).cloneNode(), null, extension.innerElem.reduce(function( map, config ) {
if (config.type === 'attr') map[config.id] = config.content;
return map;
}, {})),
'target' : '#' + extension.id
}
break;
default:
return false;
break;
}
},
addExtensions = function addExtensions( content, extensions ) {
return extensions.reduce(function( content, extension ) {
return DOM[extension.method](extension.node, extension.target, content);
}, content);
},
// Returns new document as an [object Document]
extendContent = function extendContent( content, extensions ) {
var doc = DOM.createDoc(content),
toExtend = (extensions) ?
extensions.map(function( extension ) {
return createExtension(extension, doc);
}) :
null;
var res = null;
if (toExtend) return addExtensions(doc, toExtend);
else return doc;
};
// Export public interface. Replace this by your require.js code or similar.
return {
'extendContent' : extendContent
};
}( imports )),
extendedHTMLDoc = moduleHTMLExtender.extendContent(originalHTML, configAry);
console.log(extendedHTMLDoc.documentElement.outerHTML);
// output = '<html><head><title>myTitle</title></head><body><script id="ui-boot" src="/test/test2/-co.js" data--ui-comersion="1.17"></script><script>console.log(hello world);</script></body></html>';
I have two models with a many to many relationship to each other - the first is 'recipe', the second is 'tag':
App.ApplicationAdapter = DS.FixtureAdapter.extend({});
App.Recipe = DS.Model.extend({
title: attr('string'),
source: attr('string'),
notes: attr('string'),
isFavourite: attr('boolean', {defaultValue: false}),
tags: hasMany('tag', { async: true })
});
App.Tag = DS.Model.extend({
name: attr('string'),
recipes: hasMany('recipe', { async: true })
});
In my template, I have an input for adding a tag to a recipe.
User can add either one tag, or multiple by separating each tag with a comma.
{{input type="text" enter="AddTags" placeholder="add a tag (or two!)"}}
This calls 'AddTags' within my RecipeController:
App.RecipeController = Ember.ObjectController.extend({
actions: {
AddTags: function(tags){
var store = this.store;
var thisRecipe = this.get('model');
var thisRecipeTags = thisRecipe.get('tags');
var addedTags = tags.split(',');
for (i = 0; i < addedTags.length; i++) {
tag = store.createRecord('tag', {
name: addedTags[i]
});
thisRecipeTags.addObject(tag);
}
}
}
});
This works wonderfully - however - I would like to refactor the function so that a new tag is only created if one with the same name doesn't already exist:
AddTags: function(tags){
var store = this.store;
var thisRecipe = this.get('model');
var thisRecipeTags = thisRecipe.get('tags');
var addedTags = tags.split(',');
for (i = 0; i < addedTags.length; i++) {
// If exisiting tag where name == addedTags[i]
tag = existing tag object here
// Else create a new tag
tag = store.createRecord('tag', {
name: addedTags[i]
});
thisRecipeTags.addObject(tag);
}
}
How can I do this?
Update: I have set up a JS bin with the relevant code here: http://jsbin.com/kixexe/1/edit?js,output
Working demo here
Now for some explanation...
You are using DS.FixtureAdapter in your jsbin, which requires you to implement a queryFixtures method if you are going to query fixtures (see here) like you are doing when querying by name. A quick implementation of this could be as follows:
MyApp.TagAdapter = DS.FixtureAdapter.extend({
queryFixtures: function(fixtures, query){
var result = [];
var tag = fixtures.findBy('name', query.name);
if(tag){
result.push(tag);
}
return result;
}
});
When you query the store, you get back a promise which you can go into by using then()
If you didn't find anything - you can just move on with what you had before...
If you did find an existing tag - you can add it to other tags and then save the tags and the recipe records, since this is a many-to-many relationship.
UPDATE
Updated solution here
The for loop you had was getting confused especially when using promises, this is usually NOT what you want. Instead, Ember has a more typical/functional approach of forEach() I also trimmed the whitespace on the ends of the tags, since this is probably what you want using map(). See here to learn more about map() and forEach()
// trim whitespace
var addedTags = tags.split(',').map(function(str){
return str.replace(/^\s+|\s+$/gm,'');
});
// loop through tags
addedTags.forEach(function(tagName){
store.find('tag', { name: tagName }).then(function(results){
if(results.content.length){
thisRecipeTags.addObject(results.content[0]);
thisRecipeTags.save();
thisRecipe.save();
}
else {
var tag = store.createRecord('tag', {
name: tagName
});
thisRecipeTags.addObject(tag);
thisRecipeTags.save();
thisRecipe.save();
}
});
});
UPDATE #2
The tag was not being properly removed. Because this is a 2-way mapping, apparently you need to remove tag from the recipe (which you were doing) and ALSO recipe from the tag, which you were NOT doing. The updated code looks as follows:
removeTag: function(tag) {
var recipe = this.get('model');
recipe.get('tags').removeObject(tag);
tag.get('recipes').removeObject(recipe.get('id'));
tag.save();
recipe.save();
},
Working (hopefully for real this time ;) solution here
You can search for an existing tag as follows:
var existing = store.find('tag', { name: addedTags[i] });
So, you can have logic along the lines of:
AddTags: function(tags){
var store = this.store;
var thisRecipe = this.get('model');
var thisRecipeTags = thisRecipe.get('tags');
var addedTags = tags.split(',');
for (i = 0; i < addedTags.length; i++) {
var existing = store.find('tag', { name: addedTags[i] });
if(existing){
tag = existing
} else {
tag = store.createRecord('tag', {
name: addedTags[i]
});
}
thisRecipeTags.addObject(tag);
}
}
I can't test this at the moment so it might need some changes to account for promises, etc -- but that should get you more or less what you need.
I am trying to integrate meteor with another JS framework. I had things working fairly well prior to the latest Blaze upgrade (i.e. pre-version 0.8). In order to get this to work, I need to render a meteor template as an HTML string. Moreover, I need to be able to supply a data object to provide the values for the variables included in the template.
Pre 0.8, I could simply do the following:
var myTemplateName = 'someTemplateName';
var myTemplateVariableData = {myTemplateVariableData: {'xxx': 'some data}};
var myTemplateHTML = Template[myTemplateName].render(myTemplateVariableData);
Of course, that no longer works. However, I have to think there is some way to do this still even post-0.8. I have gotten fairly close with the following:
var myTemplateVariableData = {'xxx': 'some data};
var templateName = 'someTemplateName';
var myTemplateHTML = "";
var dr = new UI.DomRange; // domain range
var loadedTemplate = Template[templateName].extend(
{
data: function () {
return { myTemplateVariableData: myTemplateVariableData }
}
}
);
var renderedTemplate = loadedTemplate.render();
UI.materialize(renderedTemplate, dr, null, dr.component);
for (var member in dr.members) {
if (dr.members.hasOwnProperty(member)) {
var testHTML = dr.members[member].innerHTML;
if (testHTML) {
myTemplateHTML = myTemplateHTML + testHTML
} else {
myTemplateHTML = myTemplateHTML + dr.members[member].textContent
}
}
}
The problem with the result of this attempt is that if I try something like this:
{{#if myTemplateVariableData.xxx}}<span> {{myTemplateVariableData.xxx}}</span>{{/if}}
I will get the span showing up but no content other than the  . It seems as though when inside of an if block, it loses the context and can't see the myTemplateVariableData attribute on the 'this' object any longer.
I suspect there is an easier way to accomplish what I am trying to do here but I am out of ideas at present so I thought I'd post this here to see if anyone else had tried to do something similar.
My solution I think is a little more straight forward:
function html(contextObject, templateName) {
return Blaze.toHTML(Blaze.With(contextObject, function() { return Template[templateName]; }));
}
use:
template file
<template name="myTemplate">
<div id="{{myid}}"></div>
</template>
in some JS file
var myhtml = html({myid : 11}, "myTemplate") //<div id="1"></div>
To answer your question in the title, you could do it like this:
var templateName = 'myTemplate'
var context = {randomNumber: 527}
var div = document.createElement('div')
var component = UI.renderWithData(Template[templateName], context)
UI.insert(component, div)
var htmlString = div.innerHTML // <-- What you want.
But that's maybe not what you want?
I have a JSON result that contains numerous records. I'd like to show the first one, but have a next button to view the second, and so on. I don't want the page to refresh which is why I'm hoping a combination of JavaScript, jQuery, and even a third party AJAX library can help.
Any suggestions?
Hope this helps:
var noName = {
data: null
,currentIndex : 0
,init: function(data) {
this.data = data;
this.show(this.data.length - 1); // show last
}
,show: function(index) {
var jsonObj = this.data[index];
if(!jsonObj) {
alert("No more data");
return;
}
this.currentIndex = index;
var title = jsonObj.title;
var text = jsonObj.text;
var next = $("<a>").attr("href","#").click(this.nextHandler).text("next");
var previous = $("<a>").attr("href","#").click(this.previousHandler).text("previous");
$("body").html("<h2>"+title+"</h2><p>"+text+"</p>");
$("body").append(previous);
$("body").append(document.createTextNode(" "));
$("body").append(next);
}
,nextHandler: function() {
noName.show(noName.currentIndex + 1);
}
,previousHandler: function() {
noName.show(noName.currentIndex - 1);
}
};
window.onload = function() {
var data = [
{"title": "Hello there", "text": "Some text"},
{"title": "Another title", "text": "Other"}
];
noName.init(data);
};
I use jqgrid for just this purpose. Works like a charm.
http://www.trirand.com/blog/
I would personally load the json data into a global variable and page it that way. Hope you don't mind my assumptions on the context of survey data I think I remember you from yesterday.
var surveyData = "[{prop1: 'value', prop2:'value'},{prop1: 'value', prop2:'value'}]"
$.curPage = 0;
$.fn.loadQuestion = function(question) {
return this.each(function() {
$(this).empty().append(question.prop1);
// other appends for other question elements
});
}
$(document).ready(function() {
$.questions = JSON.parse(surveyData); // from the json2 library json.org
$('.questionDiv').loadQuestion($.questions[0]);
$('.nextButton').click(funciton(e) {
if ($.questions.length >= $.curPage+1)
$('.questionDiv').loadQuestion($.questions[$.curPage++]);
else
$('.questionDiv').empty().append('Finished');
});
});
~ UnTested
I'll have to admit that #sktrdie approach of creating a whole plugin for handling the survey would be nice. IMO this method is really a path of least resistance solution.