What's the best approach to save and load an flowchart on jsPlumb?
I managed to save the chart by simply having all the elements inside an array of objects, where each object has source and target nodes, x, y coordinates.
When saving, simply do JSON.stringify(whole_object), and if loading, simply JSON.parse() and manually position the nodes as well as connect them.
My solution save and load jsPlumb:
function Save() {
$(".node").resizable("destroy");
Objs = [];
$('.node').each(function() {
Objs.push({id:$(this).attr('id'), html:$(this).html(),left:$(this).css('left'),top:$(this).css('top'),width:$(this).css('width'),height:$(this).css('height')});
});
console.log(Objs);
}
function Load() {
var s="";
for(var i in Objs) {
var o = Objs[i];
console.log(o);
s+='<div id="'+ o.id+'" class="node" style="left:'+ o.left+'; top:'+ o.top+'; width:'+ o.width +'; height:'+ o.height +' "> '+ o.html+'</div>';
}
$('#main').html(s);
}
UPD Demo: http://jsfiddle.net/Rra6Y/137/
Note: if demo does not work in JsFiddle, make sure it points to an existing jsPlumb link (links are listed in "External Resources" JsFiddle menu item
My save functionality does a bit more than just save the x, y position of the element and its connections. I also added saving a connection Label overlay as well as custom text for each element. You can tailor this solution as per your requirements but here it is basically:
//save functionality
function IterateDrawnElements(){ //part of save
var dict = {};
$('#id_diagram_container').children('div.window').each(function () {
var pos = $(this).position()
var diagram_label = $(this).children('div.asset-label').children('div.asset-diagram-label').text()
if (diagram_label == null || diagram_label == ''){
diagram_label='';
}
dict[this.id] = [pos.left, pos.top, diagram_label];
});
return dict;
}
function IterateConnections(){ //part of save
var list = [];
var conns = jsPlumb.getConnections()
for (var i = 0; i < conns.length; i++) {
var source = conns[i].source.id;
var target = conns[i].target.id;
try{
var label = conns[i].getOverlay("label-overlay").labelText;
}
catch(err) {
label = null
}
//list.push([source, target])
if (source != null && target != null){
list.push([source, target, label]);
};
}
return list;
}
I initiate all this when the user hits the save button, an ajax call is made back to the server, in this case Django is intercepting the ajax request and saves the data to the database.
//ajax call when save button pressed
$save_btn.click(function() {
//drawn elements
var d_elements = IterateDrawnElements();
var d_conns = IterateConnections();
var d_name =$('#id_diagram_name').val();
$.ajax({
url : ".",
type : "POST",
dataType: "json",
data : {
drawn_elements: JSON.stringify(d_elements),
conns: JSON.stringify(d_conns),
diagram_name: d_name,
csrfmiddlewaretoken: '{{ csrf_token }}'
},
success: function (result) {
if (result.success == true){
save_status.html(result.message)
}
//console.log(JSON.stringify(result));
$save_btn.attr('disabled','disabled');
if (result.old_name != false){
//alert()
$('#id_diagram_name').val(result.old_name)
}
},
error: function(xhr, textStatus, errorThrown) {
alert("Please report this error: "+errorThrown+xhr.status+xhr.responseText);
}
});
//return false; // always return error?
});
To load all this up is even easier and there are many ways you can do this. In Django you can just generate the html straight in your template as well as the js for the connections or you can create a JSON object in javascript for everything and then have javascript draw it all based on the array. I used jquery for this.
//js & connections load
var asset_conns = [
{% for conn in diagram_conns %}
[ {{ conn.source.id }}, {{ conn.target.id }}, '{{ conn.name }}' ],
{% endfor %}
]
// Takes loaded connections and connects them
for (var i = 0; i< asset_conns.length; i++){
var source = asset_conns[i][0].toString();
var target = asset_conns[i][1].toString();
var label = asset_conns[i][2];
var c = jsPlumb.connect({source: source, target: target, detachable:true, reattach: true }); //on init already know what kind of anchor to use!
if (label != null && label != 'None'){
c.addOverlay([ "Label", { label: label, id:"label-overlay"} ]);
}
}
//html right into django template to draw elements, asset element interchangeable terms
{% for element in drawn_elements %}
<div id="{{ element.asset.id }}" class="window" style="left:{{ element.left }}px;top:{{ element.top }}px;background-image: url('{% static element.asset.asset_mold.image.url %}'); width: {{ element.asset.asset_mold.image.width }}px;height: {{ element.asset.asset_mold.image.height }}px;">
<div class="asset-label" id="label-{{ element.asset.id }}">
{#{{ element.asset }}#}<a class="lbl-link" id="lbl-link-{{ element.asset.id }}" href="{{ element.asset.get_absolute_url }}">{{ element.asset }}</a>
<div class='asset-diagram-label' id="lbl-{{ element.asset.id }}">{% if element.asset.diagram_label %}{{ element.asset.diagram_label }}{% endif %}</div>
</div>
<div class='ep' id="ep-{{ element.asset.id }}"></div>
</div>
{% endfor %}
You can greatly simplify this but mine also gets a background for the element, as well as label and the shape of the element to use with perimeter anchors. This solution works and is tested. I'll release an open source Djago application for this soon on PyPi.
I'm using YUI with it. I'm saving the position of each box item being connected in a table. I them have a separate table the stores a parent to child relationship between the items, which is used to determine the lines jsPlumb should draw. I determine this using a selection process in which the first item selected is the parent, and all other items are children. When the "connect" button is clicked, the parent/child selection of the items is cleared. I also toggle this if you click the selected parent - it clear the child selections as well.
I recently wrote this blog post about why jsPlumb doesn't have a save function (and what I recommend you do):
http://jsplumb.tumblr.com/post/11297005239/why-doesnt-jsplumb-offer-a-save-function
...maybe someone will find it useful.
Related
Here is what I've done so far:
1.) I've made a javascript function that gets all the id's of the items (using checkbox select) in the database like so (this is DataTables):
function () {
// count check used for checking selected items.
var count = table.rows( { selected: true } ).count();
// Count check.
// Count must be greater than 0 to delete an item.
// if count <= 0, delete functionality won't continue.
if (count > 0) {
var data = table.rows( { selected: true } ).data();
var list = [];
for (var i=0; i < data.length ;i++){
// alert(data[i][2]);
list.push(data[i][2]);
}
var sData = list.join();
// alert(sData)
document.getElementById('delete_items_list').value = sData;
}
}
It outputs something like 1,2,5,7 depending on what rows I have selected.
2.) Passed the values inside a <input type="hidden">.
Now, I've read a post that says you can delete data in Django database using a checkbox, but I'm not sure how exactly can I use this.
I'm guessing I should put it in the ListView that I made, but how can I do that when I click the "Delete selected items" button, I can follow this answer?
I'm trying to achieve what Django Admin looks like when you delete items.
My ListView looks like this:
Yes you can use linked example. Django Admin do it the same way, You send selected ids and django do filtering by given values and after django apply selected action for selected items.
UPDATE
For example.
class List(ListView);
def post(self, request, *args, **kwargs):
ids = self.request.POST.get('ids', "")
# ids if string like "1,2,3,4"
ids = ids.split(",")
try:
# Check ids are valid numbers
ids = map(int, ids)
except ValueError as e:
return JsonResponse(status=400)
# delete items
self.model.objects.filter(id__in=ids).delete()
return JsonResponse({"status": "ok"}, status=204)
And html:
<button id="delete-button">Del</button>
<div id="items-table">
{% for object in objects_list %}
<div class="item" data-id="{{object.id}}">{{ object.name }}</div>
{% endfor %}
</div>
<script>
$(function(){
$('#delete-button').on('click', function(e) {
// Get selected items. You should update it according to your template structure.
var ids = $.map($('#items-table item'), function(item) {
return $(item).data('id')
}).join(',');
$.ajax({
type: 'POST',
url: window.location.href ,
data: {'ids': ids},
success: function (res) {
// Update page
window.location.href = window.location.href;
},
error: function () {
// Display message or something else
}
});
})
})();
</script>
I am currently having a problem:
I need to create md-checkboxes from a Database. This part works fine with ng-repeat. But I am having a problem reading those checkboxes.
Every entry in the Database has its own unique ID (I am using RethinkDB) so I thought I just can apply this as an ID.
md-card(ng-repeat="n in ideas")
md-card-content
span
md-checkbox(type="checkbox" id='n.id') {{n.idea}}
I am working with Jade / Pug as View Engine.
But how am I now able to read out all checkboxes at once?
I tried many methods like looping through all ElementsByTagName("md-checkbox") and than with a for to read the checked value but it always returns undefined.
const checkboxes = document.getElementsByTagName("md-checkbox");
console.log(checkboxes) //works fine, prints all checkboxes
for (let i = 0; i < checkboxes.length; i++) {
console.log(checkboxes[i].checked); //returns undefined
}
Do you have any Ideas how to read all Boxes at once?
Edit #1 - More Code
index.js
angular.module('votes', ['ngMaterial'])
.controller("VoteMainController", function ($scope) {
$scope.safeApply = function (fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
register = [];
//var to store database content and add it to page
$scope.ideas;
//Downloading Database stuff as JSON
$.ajax({
type: "GET",
url: "./api",
async: true,
success: function (content) {
for (let i = 0; i < content.length; i++) {
register.push({
[content[i].id]: {
checked: false
}
})
}
$scope.ideas = content;
$scope.safeApply();
},
});
function checkChecked() {
const checkboxes = document.getElementsByTagName("md-checkbox");
for (let i = 0; i < checkboxes.length; i++) {
console.log(checkboxes[i].checked);
}
}
})
index.jade
form(id="login" method="post")
md-card(ng-repeat="n in ideas")
md-card-content
span
md-checkbox(type="checkbox" id='n.id') {{n.idea}}
md-input-container
label Andere Idee?
md-icon search
input(name='idea', ng-model='idea', id='idea', type='text')
div(layout="column")
md-button(type='submit', class="md-raised md-primary unselectable")
| Senden!
Your question title asks about assigning ids but Your question, which you wrote at the very end is
"Do you have any Ideas how to read all Boxes at once?"
So if you wanna do something with multiple or all checkbox elements you should assign them some css class, say "idea-checkbox".
Then in css styles or in jQuery you can access all those checkboxes at once by using the dotted syntax of ".idea-checkbox".
css ids are used to distinctively access any one html element and classes are used to get access to all the elements at once which has that class.
You can also try to the use angular's "track by n.id" syntax if you want to track them by their id.
You should be able to do this with ng-list and ng-model:
HTML
<div ng-app="MyApp" ng-controller="MyAppController">
<ng-list>
<ng-list-item ng-repeat="n in ideas">
<p>{{n.id}}: <input type="checkbox" ng-model="n.isChecked"></p>
</ng-list-item>
</ng-list>
<p>Current state of model: {{ ideas }}</p>
</div>
<p>Current state of model: {{ ideas }}</p>
Angular
angular
.module("MyApp", ['ngMessages'])
.controller('MyAppController', AppController);
function AppController($scope) {
$scope.ideas = [{
id: 193872768,
isChecked: false
},{
id: 238923113,
isChecked: false
}];
}
Demo
This also works with the material design counterparts, md-list and md-checkbox.
I worked all day on it and impossible to figure it out so I need some help :)
I try to add some text on each variant line of my "Size" dropdown on my product page :
if the product quantity > 0 : Size + Quick Delivery
else : Size + 2-3 weeks delivery
Just want to display it to the customer before the click, so I don't want it just on the selectedVariant.
I tried to change it by my script.js, I was thinking to :
copy each variant quantity (I didn't find the way to do it)
copy my Dropdown in a list value/key + add text (Quick/2-3 weeks) depending of the variant quantity
var optionsSizes = {};
var optionsSizes = {};
$('#mySizes option').each(function(){
optionsSizes[$(this).val()] = $(this).text() + variant.inventory_quantity;
});
//console.log(optionsSizes);
var $el = $("#mySizes");
$el.empty(); // remove old options
$.each(optionsSizes, function(value,key) {
$el.append($("<option></option>")
.attr("value", value).text(key));
});
The copy/paste of the dropdown work but that's all.
It was easy to do it on variantSelected but that's not what I want.
Please feel free to ask if you have any question.
Cheers,
bkseen
('#product-select-5916642311-option-0') and $('#mySizes') this select elements are not in your theme by default. A Shopify or theme script adds these two elements based on the product's JSON information available via Shopify. Hence there is no direct way to get the desired result.
Here's the trick that can achieve what you desire.
Load all the variants and their required properties into a JSON object. To do this add <script>variantsJSON = {}</script> at the top of the liquid file.
Now load the variants in the following structure:
variantsJSON = {
"variant_option1" : {
"variant_option2_1_title" : "variant_option2_1_quantity",
"variant_option2_2_title" : "variant_option2_2_quantity",
} ....
}
To get the above structure, you need to add the following script inside {% for variant in product.variants %} or a similar loop in your liquid file.
<script>
if (!variantsJSON['{{ variant.option1 }}']){
variantsJSON['{{ variant.option1 }}'] = {}
}
{% assign availability = 'QUICK DELIVERY' %}
{% if variant.inventory_quantity == 0 %}{% assign availability = '2-3 WEEKS DELIVERY' %}{% endif %}
if (!variantsJSON['{{ variant.option1 }}']['{{ variant.option2 }}']){
variantsJSON['{{ variant.option1 }}']['{{ variant.option2 }}'] = '{{ availability }}'
}
</script>
The above snippet (possible refinement required) will load all the variants and their availability into the JSON object
Now all you need to do is trigger a function on change of $('#product-select-{{ product.id }}-option-0') which will clear all <li>s in $('#mySizes'), then populates them using the values stored in variantsJSON's variant_option2 & variant_availability of the selected variant_option1
P.S. Feel free to format my answer. I'm somehow unable to get the right formatting.
To answer Hymnz on
That's tricky but I think I can help. How are you changing the span with the classes product__inventory-count product__form-message--success ? – HymnZ 10 hours ago
Blockquote
if (variant) {
{% if settings.product_show_shipping %}
var myCount = variant['inventory_quantity'];
var myPath = this.element.find('.product__inventory-count');
if (myCount < 1){
myPath.removeClass('product__form-message--success');
myPath.addClass('product__form-message--error');
myPath.text("2-3 weeks delivery");
}else{
//myPath.toggleClass('product__form-message--success').siblings().removeClass('checked');
myPath.removeClass('product__form-message--error');
myPath.addClass('product__form-message--success');
myPath.text("Quick delivery");
}
{% endif %}
The thing is, variant is the variant selected and not going through all the product's variants.
I'm currently using a script to output related products on a Shopify website I'm managing, but I want to modify this script to grab the current product's "color" tag (E.g. "Color_Jet Black (#1)") and then use this tag to generate related products that also have this tag.
The code below is the code I'm currently using.
<div class="related-products">
<div class="container-fluid nopadding">
<div class="row grid">
</div>
</div>
</div>
<script>
$( document ).ready(function() {
// Variables
var related = $('.related-products');
var handle = "{{ product.handle }}";
var tags = [];
var appendLocation = $(".related-products .row");
var relatedProductsList = [];
var maxOutput = 4
var count = 0;
// Get Product Tags
{% for tag in product.tags %}
tags.push("{{ tag }}");
}
{% endfor %}
// Get Run JSON product tags against this product's tags
jQuery.getJSON( document.location.origin + "/products.json", function(obj) {
// Build List
jQuery.each(obj.products, function(dontcare, product) {
hasMatch(tags, product);
});
// Shuffle List
shuffle(relatedProductsList);
// Output List
jQuery.each(relatedProductsList, function( dontcare, product) {
if(count < maxOutput) {
outputMatch(product);
}
count++;
});
}).complete(function() {
// Wait for products to load
$('.product-item img').load(function() {
var item = $('.product-item');
setProductItemHeight(item);
});
});
// Checks for a match between product tags and JSON tags
function hasMatch(tags, product) {
jQuery.each(tags, function(dontcare, tag) {
if(jQuery.inArray(tag, product.tags) >= 0 && product.handle != handle) {
relatedProductsList.push(product);
}
});
}
// Outputs matches
function outputMatch (product) {
// If product has a featured image
if(product.images[0] != null) {
// Create Item
$item = $("<div class='col-md-3'><article class='product-item'><a href=" +
document.location.origin + "/products/" +
product.handle +"><div class='image-wrapper'><img class='img-responsive' src='" +
product.images[0].src + "'></div><div class='product-wrapper prod-caption caption'><h6>" +
product.title + "</h6></a><p>" + product.variants[0].price + "</p><a class='btn btn-default' href=" + document.location.origin + "/products/" + product.handle +">Buy Now</a></div></article></div>");
appendLocation.append($item);
} else if (product.images[0] == null) {
// Fallback if product object has no images
appendLocation.append("<div class='col-md-3'><article class='product-item'><div class='image-wrapper'><img class='img-responsive' src='//cdn.shopify.com/s/files/1/0878/7440/t/2/assets/default.png'></div><div class='product-wrapper prod-caption'><h3>" + product.title + "</h3><p>" + product.tags[0] + "</p><em>" + product.variants[0].price + "</em><br><a class='btn btn-default' href=" + document.location.origin + "/products/" + product.handle +">Buy Now</div></article></div>");
}
}
// Randomizes relatedProductsList
function shuffle(relatedProductsList) {
for (var n = 0; n < relatedProductsList.length - 1; n++) {
var k = n + Math.floor(Math.random() * (relatedProductsList.length - n));
var temp = relatedProductsList[k];
relatedProductsList[k] = relatedProductsList[n];
relatedProductsList[n] = temp;
}
}
});
</script>
I've attempted to modify the code to grab the current product colour tag, which works, but it doesn't output any products like the original script does. There are plenty of products with this colour tag, so that's not the problem.
The code below is what I used to grab the current product's colour tag, which does work, but nothing it outputted in the DOM.
$( document ).ready(function() {
// Variables
var related = $('.related-products');
var handle = "{{ product.handle }}";
var tags = [];
var appendLocation = $(".related-products .row");
var relatedProductsList = [];
var maxOutput = 4
var count = 0;
// Get Product Tags
{% assign color = "Color_" %}
{% for tag in product.tags %}
{% if tag contains color %}
tags.push("{{ tag }}");
}
{% endif %}
{% endfor %}
Any thoughts / help would be much appreciated.
One way to do this would be to create a new collection template that outputs json, and apply that template to your json request using the 'view' parameter.
The 'view' param simply allows you to use a different template on the fly to render content. https://docs.shopify.com/manual/configuration/store-customization/page-specific/collections/add-view-all-to-collection-pages
Here is an example json liquid template (done off the top of my head - you'll need to check which liquid tags to use):
{% layout none %} // tells shopfify not to render layout template
{
"products": [
{% for product in collection.products %}
...
{
"id": "{{product.id}}",
"title": "{{product.title}}",
"handle": "{{product.handle}}",
"image" : "{{product.featured_image}}",
"price" : "{{product.price}}"
}
{% if forloop.last == false %},{% endif %} ... don't forget the , - it mustn't be on the last entry.
....
{% endfor %}
]
}
So when the template is rendered - you'll get a list of products formatted as json. Something like this:
{
"products": [
{
"id": "12234",
"title": "foo",
"handle": "bar",
"image": "blah.jpg",
"price": "12345"
},
{
"id": "12235",
"title": "foo1",
"handle": "bar1",
"image": "blah1.jpg",
"price": "12346"
},
{
"id": "12239",
"title": "foo2",
"handle": "bar2",
"image": "blah2.jpg",
"price": "12375"
}
]
}
When making your ajax request, you will need to request the URL with the tags and view. So, if you call your new template json.liquid then you would request:
/collections/frontpage/Color_Jet-Black-(#1)?view=json
eg:
jQuery.getJSON( '/collections/frontpage/Color_Jet-Black-(#1)?view=json", function(obj) {
So in this example - 'Color_Jet Black (#1)' is the tag. You should receive only those products in the Frontpage collection that have been tagged with Color_Jet Black (#1). Because we're appending ?view=json to our request, Shopify will send these products using the json template we created*. You're then free to randomise that json and render it to the page through JavaScript.
Note in my example I've called the new collection template json.liquid because its being used to render out Json - but you're free to call it whatever you wish. Nb: 'featured_products.liquid'. However, as its being used to output Json it makes sense to call it json.liquid.
Shopify collection urls are in the following format:
http://example.com/collections/collectionname/tags?view=CustomTemplate
so
http://example.com/collections/classic/watches?view=json
Would get you all products from the classic collection tagged with watches
http://example.com/collections/classic/watches+home?view=json
Would get you all products from the classic collection tagged with watches AND home.
You don't need to hardwire the collection into the new json template. You're simple requesting the collection that you need - and telling shopify to render it out as json (using the json template you've created).
Once you have the products in the json format - you can do whatever you like with them.
I have a huge table (temporary) of different videos. Every video has a link "Choose thumbnails" for that specific video.
When you click on that link, there's a popup showing, with 8 different thumbnails for that vid.
This is my link:
<a class="bigThumbnailLink" onclick="showThumb({{stat.video_id}})">Choose</a>
**Note that the curly bracket {{ }} is the TWIG syntax, its just an "echo"
Here im just building an array
var videos = [];
{% for statThumb in stats %}
videos[{{ statThumb.video_id }}] = [];
{% for thumb in statThumb.thumbnails %}
videos[{{ statThumb.video_id }}].push('{{ thumb }}');
{% endfor %}
{% endfor %}
The outputed array looks like:
var videos = [];
videos[609417] = [];
videos[609417].push('http://1.jpg');
videos[609417].push('http://2.jpg');
videos[609420] = [];
My function
function showThumb(id){
$("#bigThumbnailArea").show();
jQuery.each(videos[id], function(i, val) {
$("#bigThumbnailArea").find("ul").append("<li><img src=" + val + "></li>");
});
}
That code is working. But everytime I click on a link, instead of showing me only the thumbnails for that video, it's adding to the array. So first time I click = 8 thumb (good), 2nd link I click = 16 thumb, 3tr link = 24 etc...
I know that is probably only the way "append()" works...I tried to replace with some other insertion method like .html() but its never the result I want. html() is returning my only 1 thumbnail every time)
Im a bit confused here on how I should do that...
Any helps?
You should empty() the ul, then append the lis to it.
function showThumb(id){
$("#bigThumbnailArea").show();
var $ul = $("#bigThumbnailArea").find("ul").empty();
$.each(videos[id], function(i, val) {
$ul.append("<li><img src=" + val + "></li>");
});
}